Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.javascript.jscomp.ControlFlowGraph.Branch; import com.google.javascript.jscomp.DataFlowAnalysis.FlowState; import com.google.javascript.jscomp.LiveVariablesAnalysis.LiveVariableLattice; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Removes local variable assignments that are useless based on information from * {@link LiveVariablesAnalysis}. If there is an assignment to variable * {@code x} and {@code x} is dead after this assignment, we know that the * current content of {@code x} will not be read and this assignment is useless. * */ class DeadAssignmentsElimination extends AbstractPostOrderCallback implements CompilerPass, ScopedCallback { private final AbstractCompiler compiler; private LiveVariablesAnalysis liveness; // Matches all assignment operators and increment/decrement operators. // Does *not* match VAR initialization, since RemoveUnusedVariables // will already remove variables that are initialized but unused. private static final Predicate<Node> matchRemovableAssigns = new Predicate<Node>() { @Override public boolean apply(Node n) { return (NodeUtil.isAssignmentOp(n) && n.getFirstChild().getType() == Token.NAME) || n.getType() == Token.INC || n.getType() == Token.DEC; } }; public DeadAssignmentsElimination(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(externs); Preconditions.checkNotNull(root); NodeTraversal.traverse(compiler, root, this); } @Override public void enterScope(NodeTraversal t) { Scope scope = t.getScope();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // Global scope _SHOULD_ work, however, liveness won't finish without // -Xmx1024 in closure. We might have to look at coding conventions for // exported variables as well. if (scope.isGlobal()) { return; } // We are not going to do any dead assignment elimination in when there is // at least one inner function because in most browsers, when there is a // closure, ALL the variables are saved (escaped). Node fnBlock = t.getScopeRoot().getLastChild(); if (NodeUtil.containsFunction(fnBlock)) { return; } // We don't do any dead assignment elimination if there are no assigns // to eliminate. :) if (!NodeUtil.has(fnBlock, matchRemovableAssigns, Predicates.<Node>alwaysTrue())) { return; } // Computes liveness information first. ControlFlowGraph<Node> cfg = t.getControlFlowGraph(); liveness = new LiveVariablesAnalysis(cfg, scope, compiler); liveness.analyze(); tryRemoveDeadAssignments(t, cfg); } @Override public void exitScope(NodeTraversal t) { } @Override public void visit(NodeTraversal t, Node n, Node parent) { } /** * Try to remove useless assignments from a control flow graph that has been * annotated with liveness information. * * @param t The node traversal. * @param cfg The control flow graph of the program annotated with liveness * information. */ private void tryRemoveDeadAssignments(NodeTraversal t, ControlFlowGraph<Node> cfg) { Iterable<DiGraphNode<Node, Branch>> nodes = cfg.getDirectedGraphNodes(); for (DiGraphNode<Node, Branch> cfgNode : nodes) { FlowState<LiveVariableLattice> state = cfgNode.getAnnotation(); Node n = cfgNode.getValue(); if (n == null) { continue; } switch (n.getType()) { case Token.IF: case Token.WHILE: case Token.DO: tryRemoveAssignment(t, NodeUtil.getConditionExpression(n), state); continue; case Token.FOR: if (!NodeUtil.isForIn(n)) { tryRemoveAssignment( t, NodeUtil.getConditionExpression(n), state); } continue; case Token.SWITCH: case Token.CASE: case Token.RETURN: if (n.hasChildren()) { tryRemoveAssignment(t, n.getFirstChild(), state); } continue; // TODO(user): case Token.VAR: Remove var a=1;a=2;..... } tryRemoveAssignment(t, n, state); } } private void tryRemoveAssignment(NodeTraversal t, Node n, FlowState<LiveVariableLattice> state) { tryRemoveAssignment(t, n, n, state); } /** *

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Determines if any local variables are dead after the instruction {@code n} * and are assigned within the subtree of {@code n}. Removes those assignments * if there are any. * * @param n Target instruction. * @param exprRoot The CFG node where the liveness information in state is * still correct. * @param state The liveness information at {@code n}. */ private void tryRemoveAssignment(NodeTraversal t, Node n, Node exprRoot, FlowState<LiveVariableLattice> state) { Node parent = n.getParent(); if (NodeUtil.isAssignmentOp(n) || n.getType() == Token.INC || n.getType() == Token.DEC) { Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); // Recurse first. Example: dead_x = dead_y = 1; We try to clean up dead_y // first. if (rhs != null) { tryRemoveAssignment(t, rhs, exprRoot, state); rhs = lhs.getNext(); } Scope scope = t.getScope(); if (!NodeUtil.isName(lhs)) { return; // Not a local variable assignment. } String name = lhs.getString(); if (!scope.isDeclared(name, false)) { return; } Var var = scope.getVar(name); if (liveness.getEscapedLocals().contains(var)) { return; // Local variable that might be escaped due to closures. } // If we have an identity assignment such as a=a, always remove it // regardless of what the liveness results because it // does not change the result afterward. if (rhs != null && NodeUtil.isName(rhs) && rhs.getString().equals(var.name) && NodeUtil.isAssign(n)) { n.removeChild(rhs); n.getParent().replaceChild(n, rhs); compiler.reportCodeChange(); return; } if (state.getOut().isLive(var)) { return; // Variable not dead. } if (state.getIn().isLive(var) && isVariableStillLiveWithinExpression(n, exprRoot, var.name)) { // The variable is killed here but it is also live before it. // This is possible if we have say: // if (X = a && a = C) {..} ; .......; a = S; // In this case we are safe to remove "a = C" because it is dead. // However if we have: // if (a = C && X = a) {..} ; .......; a = S; // removing "a = C" is NOT correct, although the live set at the node // is exactly the same. // TODO(user): We need more fine grain CFA or we need to keep track // of GEN sets when we recurse here. return; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (NodeUtil.isAssign(n)) { n.removeChild(rhs); n.getParent().replaceChild(n, rhs); } else if (NodeUtil.isAssignmentOp(n)) { n.removeChild(rhs); n.removeChild(lhs); Node op = new Node(NodeUtil.getOpFromAssignmentOp(n), lhs, rhs); parent.replaceChild(n, op); } else if (n.getType() == Token.INC || n.getType() == Token.DEC) { if (NodeUtil.isExpressionNode(parent)) { parent.replaceChild(n, new Node(Token.VOID, Node.newNumber(0).copyInformationFrom(n))); } else if(n.getType() == Token.COMMA && n != parent.getLastChild()) { parent.removeChild(n); } else if (parent.getType() == Token.FOR && !NodeUtil.isForIn(parent) && NodeUtil.getConditionExpression(parent) != n) { parent.replaceChild(n, new Node(Token.EMPTY)); } else { // Cannot replace x = a++ with x = a because that's not valid // when a is not a number. return; } } else { // Not reachable. Preconditions.checkState(false, "Unknown statement"); } compiler.reportCodeChange(); return; } else { for (Node c = n.getFirstChild(); c != null;) { Node next = c.getNext(); if (!ControlFlowGraph.isEnteringNewCfgNode(c)) { tryRemoveAssignment(t, c, exprRoot, state); } c = next; } return; } } /** * Given a variable, node n in the tree and a sub-tree denoted by exprRoot as * the root, this function returns true if there exists a read of that * variable before a write to that variable that is on the right side of n. * * For example, suppose the node is x = 1: * * y = 1, x = 1; // false, there is no reads at all. * y = 1, x = 1, print(x) // true, there is a read right of n. * y = 1, x = 1, x = 2, print(x) // false, there is a read right of n but * // it is after a write. * * @param n The current node we should look at. * @param exprRoot The node */ private boolean isVariableStillLiveWithinExpression( Node n, Node exprRoot, String variable) { while (n != exprRoot) { for(Node sibling = n.getNext(); sibling != null; sibling = sibling.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(sibling)) { VariableLiveness state = isVariableReadBeforeKill(sibling, variable); // If we see a READ

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.sourceBuffer = new char[512]; this.sourceEnd = 0; } else { if (sourceString == null) Kit.codeBug(); this.sourceString = sourceString; this.sourceEnd = sourceString.length(); } this.sourceCursor = 0; } /* This function uses the cached op, string and number fields in * TokenStream; if getToken has been called since the passed token * was scanned, the op or string printed may be incorrect. */ String tokenToString(int token) { if (Token.printTrees) { String name = Token.name(token); switch (token) { case Token.STRING: case Token.REGEXP: case Token.NAME: return name + " `" + this.string + "'"; case Token.NUMBER: return "NUMBER " + this.number; } return name; } return ""; } public static boolean isKeyword(String s) { return Token.EOF != stringToKeyword(s); } private static int stringToKeyword(String name) { // #string_id_map# // The following assumes that Token.EOF == 0 final int Id_break = Token.BREAK, Id_case = Token.CASE, Id_continue = Token.CONTINUE, Id_default = Token.DEFAULT, Id_delete = Token.DELPROP, Id_do = Token.DO, Id_else = Token.ELSE, Id_export = Token.EXPORT, Id_false = Token.FALSE, Id_for = Token.FOR, Id_function = Token.FUNCTION, Id_if = Token.IF, Id_in = Token.IN, Id_new = Token.NEW, Id_null = Token.NULL, Id_return = Token.RETURN, Id_switch = Token.SWITCH, Id_this = Token.THIS, Id_true = Token.TRUE, Id_typeof = Token.TYPEOF, Id_var = Token.VAR, Id_void = Token.VOID, Id_while = Token.WHILE, Id_with = Token.WITH, // the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c Id_abstract = Token.RESERVED, Id_boolean = Token.RESERVED, Id_byte = Token.RESERVED, Id_catch = Token.CATCH, Id_char = Token.RESERVED, Id_class = Token.RESERVED, Id_const = Token.CONST, Id_debugger = Token.DEBUGGER, Id_double = Token.RESERVED, Id_enum = Token.RESERVED, Id_extends = Token.RESERVED, Id_final = Token.RESERVED, Id_finally = Token.FINALLY,

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Id_float = Token.RESERVED, Id_goto = Token.RESERVED, Id_implements = Token.RESERVED, Id_import = Token.IMPORT, Id_instanceof = Token.INSTANCEOF, Id_int = Token.RESERVED, Id_interface = Token.RESERVED, Id_long = Token.RESERVED, Id_native = Token.RESERVED, Id_package = Token.RESERVED, Id_private = Token.RESERVED, Id_protected = Token.RESERVED, Id_public = Token.RESERVED, Id_short = Token.RESERVED, Id_static = Token.RESERVED, Id_super = Token.RESERVED, Id_synchronized = Token.RESERVED, Id_throw = Token.THROW, Id_throws = Token.RESERVED, Id_transient = Token.RESERVED, Id_try = Token.TRY, Id_volatile = Token.RESERVED; int id; String s = name; // #generated# Last update: 2001-06-01 17:45:01 CEST L0: { id = 0; String X = null; int c; L: switch (s.length()) { case 2: c=s.charAt(1); if (c=='f') { if (s.charAt(0)=='i') {id=Id_if; break L0;} } else if (c=='n') { if (s.charAt(0)=='i') {id=Id_in; break L0;} } else if (c=='o') { if (s.charAt(0)=='d') {id=Id_do; break L0;} } break L; case 3: switch (s.charAt(0)) { case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') {id=Id_for; break L0;} break L; case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') {id=Id_int; break L0;} break L; case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') {id=Id_new; break L0;} break L; case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') {id=Id_try; break L0;} break L; case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') {id=Id_var; break L0;} break L; } break L; case 4: switch (s.charAt(0)) { case 'b': X="byte";id=Id_byte; break L; case 'c': c=s.charAt(3); if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>native; break L; case 'e': c=s.charAt(0); if (c=='d') { X="delete";id=Id_delete; } else if (c=='r') { X="return";id=Id_return; } break L; case 'h': X="throws";id=Id_throws; break L; case 'm': X="import";id=Id_import; break L; case 'o': X="double";id=Id_double; break L; case 't': X="static";id=Id_static; break L; case 'u': X="public";id=Id_public; break L; case 'w': X="switch";id=Id_switch; break L; case 'x': X="export";id=Id_export; break L; case 'y': X="typeof";id=Id_typeof; break L; } break L; case 7: switch (s.charAt(1)) { case 'a': X="package";id=Id_package; break L; case 'e': X="default";id=Id_default; break L; case 'i': X="finally";id=Id_finally; break L; case 'o': X="boolean";id=Id_boolean; break L; case 'r': X="private";id=Id_private; break L; case 'x': X="extends";id=Id_extends; break L; } break L; case 8: switch (s.charAt(0)) { case 'a': X="abstract";id=Id_abstract; break L; case 'c': X="continue";id=Id_continue; break L; case 'd': X="debugger";id=Id_debugger; break L; case 'f': X="function";id=Id_function; break L; case 'v': X="volatile";id=Id_volatile; break L; } break L; case 9: c=s.charAt(0); if (c=='i') { X="interface";id=Id_interface; } else if (c=='p') { X="protected";id=Id_protected; } else if (c=='t') { X="transient";id=Id_transient; } break L; case 10: c=s.charAt(1); if (c=='m') { X="implements";id=Id_implements; } else if (c=='n') { X="instanceof";id=Id_instanceof; } break L; case 12: X="synchronized";id=Id_synchronized; break L; } if (X!=null && X!=s && !X.equals(s)) id = 0; } // #/generated# // #/string_id_map# if (id == 0) { return Token.EOF; } return id & 0xff; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> public static boolean isJSIdentifier(String s) { int length = s.length(); if (length == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) return false; for (int i=1; i<length; i++) { char c = s.charAt(i); if (!Character.isJavaIdentifierPart(c)) { if (c == '\\') { if (! ((i + 5) < length) && (s.charAt(i + 1) == 'u') && 0 <= Kit.xDigitToInt(s.charAt(i + 2), 0) && 0 <= Kit.xDigitToInt(s.charAt(i + 3), 0) && 0 <= Kit.xDigitToInt(s.charAt(i + 4), 0) && 0 <= Kit.xDigitToInt(s.charAt(i + 5), 0)) { return true; } } return false; } } return true; } protected final int getLineno() { return lineno; } protected final int getCharno() { return charno; } final String getString() { return string; } final double getNumber() { return number; } final boolean eof() { return hitEOF; } public final int getToken() throws IOException { tokenno++; // Check for pushed-back token if (this.pushbackToken != Token.EOF) { int result = this.pushbackToken; this.pushbackToken = Token.EOF; return result; } int c; retry: for (;;) { // Eat whitespace, possibly sensitive to newlines. for (;;) { charno = -1; c = getChar(); if (c == EOF_CHAR) { return Token.EOF; } else if (c == '\n') { dirtyLine = false; return Token.EOL; } else if (!isJSSpace(c)) { if (c != '-') { dirtyLine = true; } break; } } if (c == '@') return Token.XMLATTR; // identifier/keyword/instanceof? // watch out for starting with a <backslash> boolean identifierStart; boolean isUnicodeEscapeStart = false; if (c == '\\') { c = getChar(); if (c == 'u') { identifierStart = true; isUnicodeEscapeStart = true; stringBufferTop = 0; } else { identifierStart = false; ungetChar(c); c = '\\'; } } else { identifierStart = Character.isJavaIdentifierStart((char)c); if (identifierStart) { stringBufferTop = 0; addToString(c); } } if (identifierStart) { boolean containsEscape = isUnicodeEscapeStart; for (;;) { if (isUnicodeEscapeStart) { // strictly speaking we

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> should probably push-back // all the bad characters if the <backslash>uXXXX // sequence is malformed. But since there isn't a // correct context(is there?) for a bad Unicode // escape sequence in an identifier, we can report // an error here. int escapeVal = 0; for (int i = 0; i != 4; ++i) { c = getChar(); escapeVal = Kit.xDigitToInt(c, escapeVal); // Next check takes care about c < 0 and bad escape if (escapeVal < 0) { break; } } if (escapeVal < 0) { parser.addError("msg.invalid.escape"); return Token.ERROR; } addToString(escapeVal); isUnicodeEscapeStart = false; } else { c = getChar(); if (c == '\\') { c = getChar(); if (c == 'u') { isUnicodeEscapeStart = true; containsEscape = true; } else { parser.addError("msg.illegal.character"); return Token.ERROR; } } else { if (c == EOF_CHAR || !Character.isJavaIdentifierPart((char)c)) { break; } addToString(c); } } } ungetChar(c); String str = getStringFromBuffer(); if (!containsEscape) { // OPT we shouldn't have to make a string (object!) to // check if it's a keyword. // Return the corresponding token if it's a keyword int result = stringToKeyword(str); if (result != Token.EOF) { if (result != Token.RESERVED) { return result; } else if (!parser.compilerEnv. isReservedKeywordAsIdentifier()) { return result; } else { // If implementation permits to use future reserved // keywords in violation with the EcmaScript // standard, treat it as name but issue warning parser.addWarning("msg.reserved.keyword", str); } } } this.string = (String)allStrings.intern(str); return Token.NAME; } // is it a number? if (isDigit(c) || (c == '.' && isDigit(peekChar()))) { stringBufferTop = 0; int base = 10; if (c == '0') { c = getChar(); if (c == 'x' || c == 'X') { base = 16; c = getChar(); } else if (isDigit(c)) { base = 8; } else { addToString('0'); } } if (base == 16) { while (0 <= Kit.xDigitToInt(c, 0)) { addToString(c); c = getChar(); } } else { while ('0' <= c && c <= '9') {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> /* * We permit 08 and 09 as decimal numbers, which * makes our behavior a superset of the ECMA * numeric grammar. We might not always be so * permissive, so we warn about it. */ if (base == 8 && c >= '8') { parser.addWarning("msg.bad.octal.literal", c == '8' ? "8" : "9"); base = 10; } addToString(c); c = getChar(); } } boolean isInteger = true; if (base == 10 && (c == '.' || c == 'e' || c == 'E')) { isInteger = false; if (c == '.') { do { addToString(c); c = getChar(); } while (isDigit(c)); } if (c == 'e' || c == 'E') { addToString(c); c = getChar(); if (c == '+' || c == '-') { addToString(c); c = getChar(); } if (!isDigit(c)) { parser.addError("msg.missing.exponent"); return Token.ERROR; } do { addToString(c); c = getChar(); } while (isDigit(c)); } } ungetChar(c); String numString = getStringFromBuffer(); double dval; if (base == 10 && !isInteger) { try { // Use Java conversion to number from string... dval = Double.valueOf(numString).doubleValue(); } catch (NumberFormatException ex) { parser.addError("msg.caught.nfe"); return Token.ERROR; } } else { dval = ScriptRuntime.stringToNumber(numString, 0, base); } this.number = dval; return Token.NUMBER; } // is it a string? if (c == '"' || c == '\'') { // We attempt to accumulate a string the fast way, by // building it directly out of the reader. But if there // are any escaped characters in the string, we revert to // building it out of a StringBuffer. int quoteChar = c; stringBufferTop = 0; c = getChar(); strLoop: while (c != quoteChar) { if (c == '\n' || c == EOF_CHAR) { ungetChar(c); parser.addError("msg.unterminated.string.lit"); return Token.ERROR; } if (c == '\\') { // We've hit an escaped character int escapeVal; c = getChar(); switch (c) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> 't': c = '\t'; break; // \v a late addition to the ECMA spec, // it is not in Java, so use 0xb case 'v': c = 0xb; break; case 'u': // Get 4 hex digits; if the u escape is not // followed by 4 hex digits, use 'u' + the // literal character sequence that follows. int escapeStart = stringBufferTop; addToString('u'); escapeVal = 0; for (int i = 0; i != 4; ++i) { c = getChar(); escapeVal = Kit.xDigitToInt(c, escapeVal); if (escapeVal < 0) { continue strLoop; } addToString(c); } // prepare for replace of stored 'u' sequence // by escape value stringBufferTop = escapeStart; c = escapeVal; break; case 'x': // Get 2 hex digits, defaulting to 'x'+literal // sequence, as above. c = getChar(); escapeVal = Kit.xDigitToInt(c, 0); if (escapeVal < 0) { addToString('x'); continue strLoop; } else { int c1 = c; c = getChar(); escapeVal = Kit.xDigitToInt(c, escapeVal); if (escapeVal < 0) { addToString('x'); addToString(c1); continue strLoop; } else { // got 2 hex digits c = escapeVal; } } break; case '\n': // Remove line terminator after escape to follow // SpiderMonkey and C/C++ c = getChar(); continue strLoop; default: if ('0' <= c && c < '8') { int val = c - '0'; c = getChar(); if ('0' <= c && c < '8') { val = 8 * val + c - '0'; c = getChar(); if ('0' <= c && c < '8' && val <= 037) { // c is 3rd char of octal sequence only // if the resulting val <= 0377 val = 8 * val + c - '0'; c = getChar(); } } ungetChar(c); c = val; } } } addToString(c); c = getChar(); } String str = getStringFromBuffer(); this.string = (String)allStrings.intern(str); return Token.STRING; } switch (c) { case ';': return Token.SEMI; case '[': return Token.LB; case ']': return Token.RB; case '{': return Token.LC; case '}': return Token.RC; case '(': return Token.LP; case ')': return Token.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>RP; case ',': return Token.COMMA; case '?': return Token.HOOK; case ':': if (matchChar(':')) { return Token.COLONCOLON; } else { return Token.COLON; } case '.': if (matchChar('.')) { return Token.DOTDOT; } else if (matchChar('(')) { return Token.DOTQUERY; } else { return Token.DOT; } case '|': if (matchChar('|')) { return Token.OR; } else if (matchChar('=')) { return Token.ASSIGN_BITOR; } else { return Token.BITOR; } case '^': if (matchChar('=')) { return Token.ASSIGN_BITXOR; } else { return Token.BITXOR; } case '&': if (matchChar('&')) { return Token.AND; } else if (matchChar('=')) { return Token.ASSIGN_BITAND; } else { return Token.BITAND; } case '=': if (matchChar('=')) { if (matchChar('=')) return Token.SHEQ; else return Token.EQ; } else { return Token.ASSIGN; } case '!': if (matchChar('=')) { if (matchChar('=')) return Token.SHNE; else return Token.NE; } else { return Token.NOT; } case '<': /* NB:treat HTML begin-comment as comment-till-eol */ if (matchChar('!')) { if (matchChar('-')) { if (matchChar('-')) { skipLine(); continue retry; } ungetChar('-'); } ungetChar('!'); } if (matchChar('<')) { if (matchChar('=')) { return Token.ASSIGN_LSH; } else { return Token.LSH; } } else { if (matchChar('=')) { return Token.LE; } else { return Token.LT; } } case '>': if (matchChar('>')) { if (matchChar('>')) { if (matchChar('=')) { return Token.ASSIGN_URSH; } else { return Token.URSH; } } else { if (matchChar('=')) { return Token.ASSIGN_RSH; } else { return Token.RSH; } } } else { if (matchChar('=')) { return Token.GE; } else { return Token.GT; } } case '*': if (matchChar('=')) { return Token.ASSIGN_MUL; } else { return Token.MUL; } case '/': // is it a // comment? if (matchChar('/')) { skipLine(); continue retry; } if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>matchChar('*')) { while ((c = getChar()) != EOF_CHAR && !(c == '*' && matchChar('/'))) { // empty loop body } if (c == EOF_CHAR) { parser.addError("msg.unterminated.comment"); return Token.ERROR; } continue retry; } if (matchChar('=')) { return Token.ASSIGN_DIV; } else { return Token.DIV; } case '%': if (matchChar('=')) { return Token.ASSIGN_MOD; } else { return Token.MOD; } case '~': return Token.BITNOT; case '+': if (matchChar('=')) { return Token.ASSIGN_ADD; } else if (matchChar('+')) { return Token.INC; } else { return Token.ADD; } case '-': if (matchChar('=')) { c = Token.ASSIGN_SUB; } else if (matchChar('-')) { if (!dirtyLine) { // treat HTML end-comment after possible whitespace // after line start as comment-utill-eol if (matchChar('>')) { skipLine(); continue retry; } } c = Token.DEC; } else { c = Token.SUB; } dirtyLine = true; return c; default: parser.addError("msg.illegal.character"); return Token.ERROR; } } } /** * Tokenizes JSDoc comments. */ @SuppressWarnings("fallthrough") final int getJSDocToken() throws IOException { int c; stringBufferTop = 0; for (;;) { // eat white spaces for (;;) { charno = -1; c = getChar(); if (c == EOF_CHAR) { return Token.EOF; } else if (c == '\n') { return Token.EOL; } else if (!isJSSpace(c)) { break; } } switch (c) { // annotation, e.g. @type or @constructor case '@': do { c = getChar(); if (isAlpha(c)) { addToString(c); } else { ungetChar(c); this.string = getStringFromBuffer(); stringBufferTop = 0; return Token.ANNOTATION; } } while (true); case '*': if (matchChar('/')) { return Token.EOC; } else { return Token.STAR; } case ',': return Token.COMMA; case '>': return Token.GT; case '(': return Token.LP; case ')': return Token.RP; case '{': return Token.LC; case '}': return Token.RC; case '[': return Token.LB; case ']': return Token.RB; case '?': return Token.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>QMARK; case '!': return Token.BANG; case ':': return Token.COLON; case '=': return Token.EQUALS; case '|': matchChar('|'); return Token.PIPE; case '.': c = getChar(); if (c == '<') { return Token.LT; } else { if (c == '.') { c = getChar(); if (c == '.') { return Token.ELLIPSIS; } else { addToString('.'); } } // we may backtrack across line boundary ungetBuffer[ungetCursor++] = c; c = '.'; } // fall through default: { // recognize a jsdoc string but discard last . if it is followed by // a non-jsdoc comment char, e.g. Array.< int c1 = c; addToString(c); int c2 = getChar(); if (!isJSDocString(c2)) { ungetChar(c2); this.string = getStringFromBuffer(); stringBufferTop = 0; return Token.STRING; } else { do { c1 = c2; c2 = getChar(); if (c1 == '.' && c2 == '<') { ungetChar(c2); ungetChar(c1); this.string = getStringFromBuffer(); stringBufferTop = 0; return Token.STRING; } else { if (isJSDocString(c2)) { addToString(c1); } else { ungetChar(c2); addToString(c1); this.string = getStringFromBuffer(); stringBufferTop = 0; return Token.STRING; } } } while (true); } } } } } /** * Gets the remaining JSDoc line without the {@link Token#EOL}, * {@link Token#EOF} or {@link Token#EOC}. */ @SuppressWarnings("fallthrough") String getRemainingJSDocLine() throws IOException { int c; for (;;) { c = getChar(); switch (c) { case '*': if (peekChar() != '/') { addToString(c); break; } // fall through case EOF_CHAR: case '\n': ungetChar(c); this.string = getStringFromBuffer(); stringBufferTop = 0; return this.string; default: addToString(c); break; } } } private boolean isJSDocString(int c) { switch (c) { case '@': case '*': case ',': case '>': case ':': case '(': case ')': case '{': case '}': case '[': case ']': case '?': case '!': case '|': case '=': case EOF_CHAR: case '\n': return false

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; default: return !isJSSpace(c); } } private static boolean isAlpha(int c) { // Use 'Z' < 'a' if (c <= 'Z') { return 'A' <= c; } else { return 'a' <= c && c <= 'z'; } } static boolean isDigit(int c) { return '0' <= c && c <= '9'; } /** * Tests whether the character is a valid JavaScript white space character * as defined in ECMAScript 3rd edition. * * Note: jsscan.c uses C isspace() (which allows * \v, I think.) note that code in getChar() implicitly accepts * '\r' == \u000D as well. */ static boolean isJSSpace(int c) { if (c <= 127) { return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB; } else { return c == 0xA0 || Character.getType((char)c) == Character.SPACE_SEPARATOR; } } private static boolean isJSFormatChar(int c) { return c > 127 && Character.getType((char)c) == Character.FORMAT; } /** * Gets the accumulated {@link JSDocInfo} and resets it. * Obsolete */ JSDocInfo getAndResetJSDocInfo() { return null; } /** * Returns any {@link JSDocInfo} with a fileoverview tag that showed up. * Obsolete */ JSDocInfo getFileOverviewJSDocInfo() { return null; } /** * Returns whether any {@link JSDocInfo} was accumulated. * Obsolete */ boolean isPopulated() { return false; } /** * Parser calls the method when it gets / or /= in literal context. */ void readRegExp(int startToken) throws IOException { stringBufferTop = 0; if (startToken == Token.ASSIGN_DIV) { // Miss-scanned /= addToString('='); } else { if (startToken != Token.DIV) Kit.codeBug(); } boolean inCharSet = false; // true if inside a '['..']' pair int c; while ((c = getChar()) != '/' || inCharSet) { if (c == '\n' || c == EOF_CHAR) { ungetChar(c); throw parser.reportError("msg.unterminated.re.lit"); } if (c == '\\') { addToString(c); c = getChar(); } else if (c == '[') { inCharSet = true; } else if (c == ']') { inCharSet = false; } addToString

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(c); } int reEnd = stringBufferTop; while (true) { if (matchChar('g')) addToString('g'); else if (matchChar('i')) addToString('i'); else if (matchChar('m')) addToString('m'); else break; } if (isAlpha(peekChar())) { throw parser.reportError("msg.invalid.re.flag"); } this.string = new String(stringBuffer, 0, reEnd); this.regExpFlags = new String(stringBuffer, reEnd, stringBufferTop - reEnd); } boolean isXMLAttribute() { return xmlIsAttribute; } int getFirstXMLToken() throws IOException { xmlOpenTagsCount = 0; xmlIsAttribute = false; xmlIsTagContent = false; ungetChar('<'); return getNextXMLToken(); } int getNextXMLToken() throws IOException { stringBufferTop = 0; // remember the XML for (int c = getChar(); c != EOF_CHAR; c = getChar()) { if (xmlIsTagContent) { switch (c) { case '>': addToString(c); xmlIsTagContent = false; xmlIsAttribute = false; break; case '/': addToString(c); if (peekChar() == '>') { c = getChar(); addToString(c); xmlIsTagContent = false; xmlOpenTagsCount--; } break; case '{': ungetChar(c); this.string = getStringFromBuffer(); return Token.XML; case '\'': case '"': addToString(c); if (!readQuotedString(c)) return Token.ERROR; break; case '=': addToString(c); xmlIsAttribute = true; break; case ' ': case '\t': case '\r': case '\n': addToString(c); break; default: addToString(c); xmlIsAttribute = false; break; } if (!xmlIsTagContent && xmlOpenTagsCount == 0) { this.string = getStringFromBuffer(); return Token.XMLEND; } } else { switch (c) { case '<': addToString(c); c = peekChar(); switch (c) { case '!': c = getChar(); // Skip ! addToString(c); c = peekChar(); switch (c) { case '-': c = getChar(); // Skip - addToString(c); c = getChar(); if (c == '-') { addToString(c); if(!readXmlComment()) return Token.ERROR; } else { // throw away the string in progress stringBufferTop = 0; this.string = null; parser.addError("msg.XML.bad.form"); return Token.ERROR; } break;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case '[': c = getChar(); // Skip [ addToString(c); if (getChar() == 'C' && getChar() == 'D' && getChar() == 'A' && getChar() == 'T' && getChar() == 'A' && getChar() == '[') { addToString('C'); addToString('D'); addToString('A'); addToString('T'); addToString('A'); addToString('['); if (!readCDATA()) return Token.ERROR; } else { // throw away the string in progress stringBufferTop = 0; this.string = null; parser.addError("msg.XML.bad.form"); return Token.ERROR; } break; default: if(!readEntity()) return Token.ERROR; break; } break; case '?': c = getChar(); // Skip ? addToString(c); if (!readPI()) return Token.ERROR; break; case '/': // End tag c = getChar(); // Skip / addToString(c); if (xmlOpenTagsCount == 0) { // throw away the string in progress stringBufferTop = 0; this.string = null; parser.addError("msg.XML.bad.form"); return Token.ERROR; } xmlIsTagContent = true; xmlOpenTagsCount--; break; default: // Start tag xmlIsTagContent = true; xmlOpenTagsCount++; break; } break; case '{': ungetChar(c); this.string = getStringFromBuffer(); return Token.XML; default: addToString(c); break; } } } stringBufferTop = 0; // throw away the string in progress this.string = null; parser.addError("msg.XML.bad.form"); return Token.ERROR; } /** * */ private boolean readQuotedString(int quote) throws IOException { for (int c = getChar(); c != EOF_CHAR; c = getChar()) { addToString(c); if (c == quote) return true; } stringBufferTop = 0; // throw away the string in progress this.string = null; parser.addError("msg.XML.bad.form"); return false; } /** * */ private boolean readXmlComment() throws IOException { for (int c = getChar(); c != EOF_CHAR;) { addToString(c); if (c == '-' && peekChar() == '-') { c = getChar(); addToString(c); if (peekChar() == '>') { c = getChar(); // Skip > addToString(c); return true; } else { continue; } } c = getChar(); } stringBufferTop = 0; // throw away the string

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.get(v); // NOTE(nicksantos): Don't handle variables that are never used. // The tests are much easier to write if you don't, and there's // another pass that handles unused variables much more elegantly. if (referenceInfo != null && referenceInfo.references.size() >= 2 && referenceInfo.isWellDefined() && referenceInfo.isAssignedOnceInLifetime()) { Reference init = referenceInfo.getInitializingReference(); Node value = init.getAssignedValue(); if (value != null && value.getType() == Token.NAME) { aliasCandidates.put(value, new AliasCandidate(v, referenceInfo)); } } } } } /** * For all variables in this scope, see if they are only used once. * If it looks safe to do so, inline them. */ private void doInlinesForScope(NodeTraversal t, Map<Var, ReferenceCollection> referenceMap) { for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); ReferenceCollection referenceInfo = referenceMap.get(v); // referenceInfo will be null if we're in constants-only mode // and the variable is not a constant. if (referenceInfo == null || isVarInlineForbidden(v)) { // Never try to inline exported variables or variables that // were not collected or variables that have already been inlined. continue; } else if (isInlineableDeclaredConstant(v, referenceInfo)) { Reference init = referenceInfo.getInitializingReferenceForConstants(); Node value = init.getAssignedValue(); inlineDeclaredConstant(v, value, referenceInfo.references); staleVars.add(v); } else if (mode == Mode.CONSTANTS_ONLY) { // If we're in constants-only mode, don't run more aggressive // inlining heuristics. See InlineConstantsTest. continue; } else { inlineNonConstants(v, referenceInfo); } } } private void inlineNonConstants( Var v, ReferenceCollection referenceInfo) { int refCount = referenceInfo.references.size(); Reference declaration = referenceInfo.references.get(0); Reference init = referenceInfo.getInitializingReference(); int firstRefAfterInit = (declaration == init) ? 2 : 3; if (refCount > 1 && isImmutableAndWellDefinedVariable(v, referenceInfo)) { // if the variable is referenced more than once, we can only // inline it if it's immutable and never defined before referenced. Node value; if (init != null) { value = init.getAssignedValue(); } else { // Create a new node for variable that is never initialized. Node srcLocation = declaration.getNameNode(); value = NodeUtil.newUndefinedNode(srcLocation); } Preconditions.checkNotNull(value); inlineWellDefinedVariable(v, value, referenceInfo.references);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> staleVars.add(v); } else if (refCount == firstRefAfterInit) { // The variable likely only read once, try some more // complex inlining heuristics. Reference reference = referenceInfo.references.get( firstRefAfterInit - 1); if (canInline(declaration, init, reference)) { inline(v, declaration, init, reference); staleVars.add(v); } } else if (declaration != init && refCount == 2) { if (isValidDeclaration(declaration) && isValidInitialization(init)) { // The only reference is the initialization, remove the assignment and // the variable declaration. Node value = init.getAssignedValue(); Preconditions.checkNotNull(value); inlineWellDefinedVariable(v, value, referenceInfo.references); staleVars.add(v); } } // If this variable was not inlined normally, check if we can // inline an alias of it. (If the variable was inlined, then the // reference data is out of sync. We're better off just waiting for // the next pass.) if (!staleVars.contains(v) && referenceInfo.isWellDefined() && referenceInfo.isAssignedOnceInLifetime()) { List<Reference> refs = referenceInfo.references; for (int i = 1 /* start from a read */; i < refs.size(); i++) { Node nameNode = refs.get(i).getNameNode(); if (aliasCandidates.containsKey(nameNode)) { AliasCandidate candidate = aliasCandidates.get(nameNode); if (!staleVars.contains(candidate.alias)) { Reference aliasInit; aliasInit = candidate.refInfo.getInitializingReference(); Node value = aliasInit.getAssignedValue(); Preconditions.checkNotNull(value); inlineWellDefinedVariable(candidate.alias, value, candidate.refInfo.references); staleVars.add(candidate.alias); } } } } } /** * If there are any variable references in the given node tree, blacklist * them to prevent the pass from trying to inline the variable. */ private void blacklistVarReferencesInTree(Node root, Scope scope) { for (Node c = root.getFirstChild(); c != null; c = c.getNext()) { blacklistVarReferencesInTree(c, scope); } if (root.getType() == Token.NAME) { staleVars.add(scope.getVar(root.getString())); } } /** * Whether the given variable is forbidden from being inlined. */ private boolean isVarInlineForbidden(Var var) { // A variable may not be inlined if: // 1) The variable is exported, // 2) A reference to the variable has been inlined. We're downstream // of the mechanism that creates variable references, so we don't // have a good way to update the reference. Just punt on it. // 3) Don't inline the special RENAME

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>_PROPERTY_FUNCTION_NAME return compiler.getCodingConvention().isExported(var.name) || RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(var.name) || staleVars.contains(var); } /** * Do the actual work of inlining a single declaration into a single * reference. */ private void inline(Var v, Reference declaration, Reference init, Reference reference) { Node value = init.getAssignedValue(); Preconditions.checkState(value != null); // Check for function declarations before the value is moved in the AST. boolean isFunctionDeclaration = NodeUtil.isFunctionDeclaration(value); inlineValue(v, reference, value.detachFromParent()); if (declaration != init) { Node expressRoot = init.getGrandparent(); Preconditions.checkState(expressRoot.getType() == Token.EXPR_RESULT); NodeUtil.removeChild(expressRoot.getParent(), expressRoot); } // Function declarations have already been removed. if (!isFunctionDeclaration) { removeDeclaration(declaration); } else { compiler.reportCodeChange(); } } /** * Inline an immutable variable into all of its references. */ private void inlineWellDefinedVariable(Var v, Node value, List<Reference> refSet) { Reference decl = refSet.get(0); for (int i = 1; i < refSet.size(); i++) { inlineValue(v, refSet.get(i), value.cloneTree()); } removeDeclaration(decl); } /** * Inline a declared constant. */ private void inlineDeclaredConstant(Var v, Node value, List<Reference> refSet) { // Replace the references with the constant value Reference decl = null; for (Reference r : refSet) { if (r.getNameNode() == v.getNameNode()) { decl = r; } else { inlineValue(v, r, value.cloneTree()); } } removeDeclaration(decl); } /** * Remove the given VAR declaration. */ private void removeDeclaration(Reference declaration) { Node varNode = declaration.getParent(); varNode.removeChild(declaration.getNameNode()); // Remove var node if empty if (!varNode.hasChildren()) { Preconditions.checkState(varNode.getType() == Token.VAR); Node grandparent = declaration.getGrandparent(); NodeUtil.removeChild(grandparent, varNode); } compiler.reportCodeChange(); } /** * Replace the given reference with the given value node. * * @param v The variable that's referenced. * @param ref The reference to replace. * @param value The node tree to replace it with. This tree should be safe * to re-parent. */ private void inlineValue(Var v, Reference ref, Node value) { if (ref.isSimpleAssignmentToName()) { // This is the initial assignment. ref.getGrandparent().

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>replaceChild(ref.getParent(), value); } else { ref.getParent().replaceChild(ref.getNameNode(), value); } blacklistVarReferencesInTree(value, v.scope); compiler.reportCodeChange(); } /** * Determines whether the given variable is declared as a constant * and may be inlined. */ private boolean isInlineableDeclaredConstant(Var var, ReferenceCollection refInfo) { if (!identifyConstants.apply(var)) { return false; } if (!refInfo.isAssignedOnceInLifetime()) { return false; } Reference init = refInfo.getInitializingReferenceForConstants(); if (init == null) { return false; } Node value = init.getAssignedValue(); if (value == null) { // This constant is either externally defined or initialized indirectly // (e.g. in an function expression used to hide // temporary variables), so the constant is ineligible for inlining. return false; } // Is the constant's value immutable? if (!NodeUtil.isImmutableValue(value)) { return false; } // Determine if we should really inline a String or not. return value.getType() != Token.STRING || isStringWorthInlining(var, refInfo.references); } /** * Compute whether the given string is worth inlining. */ private boolean isStringWorthInlining(Var var, List<Reference> refs) { if (!inlineAllStrings && !var.isDefine()) { int len = var.getInitialValue().getString().length() + "''".length(); // if not inlined: var xx="value"; .. xx .. xx .. // The 4 bytes per reference is just a heuristic: // 2 bytes per var name plus maybe 2 bytes if we don't inline, e.g. // in the case of "foo " + CONST + " bar" int noInlineBytes = "var xx=;".length() + len + 4 * (refs.size() - 1); // if inlined: // I'm going to assume that half of the quotes will be eliminated // thanks to constant folding, therefore I subtract 1 (2/2=1) from // the string length. int inlineBytes = (len - 1) * (refs.size() - 1); // Not inlining if doing so uses more bytes, or this constant is being // defined. return noInlineBytes >= inlineBytes; } return true; } /** * @return true if the provided reference and declaration can be safely * inlined according to our criteria */ private boolean canInline( Reference declaration, Reference initialization, Reference reference) { if (!isValidDeclaration(declaration) || !isValidInitialization(initialization) || !isValidReference(reference)) { return false; } // If the value is read more than once, skip it. // VAR declarations and EXPR_RESULT don

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>'t need the value, but other // ASSIGN expressions parents do. if (declaration != initialization && initialization.getGrandparent().getType() != Token.EXPR_RESULT) { return false; } // Be very conservative and do no cross control structures or // scope boundaries if (declaration.getBasicBlock() != initialization.getBasicBlock() || declaration.getBasicBlock() != reference.getBasicBlock()) { return false; } // Do not inline into a call node. This would change // the context in which it was being called. For example, // var a = b.c; // a(); // should not be inlined, because it calls a in the context of b // rather than the context of the window. // var a = b.c; // f(a) // is ok. Node value = initialization.getAssignedValue(); Preconditions.checkState(value != null); if (value.getType() == Token.GETPROP && reference.getParent().getType() == Token.CALL && reference.getParent().getFirstChild() == reference.getNameNode()) { return false; } // Bug 2388531: Don't inline subclass definitions into class defining // calls as this confused class removing logic. if (value.getType() == Token.FUNCTION) { Node callNode = reference.getParent(); if (reference.getParent().getType() == Token.CALL) { SubclassRelationship relationship = compiler.getCodingConvention().getClassesDefinedByCall(callNode); if (relationship != null) { return false; } } } return canMoveAggressively(value) || canMoveModerately(initialization, reference); } /** * If the value is a literal, we can cross more boundaries to inline it. */ private boolean canMoveAggressively(Node value) { // Function expressions and other mutable objects can move within // the same basic block. return NodeUtil.isLiteralValue(value, true) || value.getType() == Token.FUNCTION; } /** * If the value of a variable is not constant, then it may read or modify * state. Therefore it cannot be moved past anything else that may modify * the value being read or read values that are modified. */ private boolean canMoveModerately( Reference initialization, Reference reference) { // Check if declaration can be inlined without passing // any side-effect causing nodes. Iterator<Node> it; if (initialization.getParent().getType() == Token.VAR) { it = NodeIterators.LocalVarMotion.forVar( initialization.getNameNode(), // NAME initialization.getParent(), // VAR initialization.getGrandparent()); // VAR container } else if (initialization.getParent().getType() == Token.ASSIGN) { Preconditions.checkState( initialization.getGrandparent().getType() == Token.EXPR_RESULT); it = NodeIterators.LocalVarMotion.forAssign(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> initialization.getNameNode(), // NAME initialization.getParent(), // ASSIGN initialization.getGrandparent(), // EXPR_RESULT initialization.getGrandparent().getParent()); // EXPR container } else { throw new IllegalStateException("Unexpected initialization parent " + initialization.getParent().toStringTree()); } Node targetName = reference.getNameNode(); while (it.hasNext()) { Node curNode = it.next(); if (curNode == targetName) { return true; } } return false; } /** * @return true if the reference is a normal VAR or FUNCTION declaration. */ private boolean isValidDeclaration(Reference declaration) { return (declaration.getParent().getType() == Token.VAR && declaration.getGrandparent().getType() != Token.FOR) || NodeUtil.isFunctionDeclaration(declaration.getParent()); } /** * @return Whether there is a initial value. */ private boolean isValidInitialization(Reference initialization) { if (initialization == null) { return false; } else if (initialization.isDeclaration()) { // The reference is a FUNCTION declaration or normal VAR declaration // with a value. return NodeUtil.isFunctionDeclaration(initialization.getParent()) || initialization.getNameNode().getFirstChild() != null; } else { Node parent = initialization.getParent(); Preconditions.checkState( parent.getType() == Token.ASSIGN && parent.getFirstChild() == initialization.getNameNode()); return true; } } /** * @return true if the reference is a candidate for inlining */ private boolean isValidReference(Reference reference) { return !reference.isDeclaration() && !reference.isLvalue(); } /** * Determines whether the reference collection describes a variable that * is initialized to an immutable value, never modified, and defined before * every reference. */ private boolean isImmutableAndWellDefinedVariable(Var v, ReferenceCollection refInfo) { List<Reference> refSet = refInfo.references; int startingReadRef = 1; Reference refDecl = refSet.get(0); if (!isValidDeclaration(refDecl)) { return false; } boolean isNeverAssigned = refInfo.isNeverAssigned(); // For values that are never assigned, only the references need to be // checked. if (!isNeverAssigned) { Reference refInit = refInfo.getInitializingReference(); if (!isValidInitialization(refInit)) { return false; } if (refDecl != refInit) { Preconditions.checkState(refInit == refSet.get(1)); startingReadRef = 2; } if (!refInfo.isWellDefined()) { return false; } Node value = refInit.getAssignedValue(); Preconditions.checkNotNull(value); boolean isImmutableValueWorthInlining = NodeUtil.isImmutableValue(value) && (value.getType() != Token.STRING || isStringWorthInlining(v, refInfo.references)); boolean isInlin

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ableThisAlias = value.getType() == Token.THIS && !refInfo.isEscaped(); if (!isImmutableValueWorthInlining && !isInlinableThisAlias) { return false; } } for (int i = startingReadRef; i < refSet.size(); i++) { Reference ref = refSet.get(i); if (!isValidReference(ref)) { return false; } } return true; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.nio.charset.Charset; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; /** * CodePrinter prints out js code in either pretty format or compact format. * * @see CodeGenerator */ class CodePrinter { // The number of characters after which we insert a line break in the code static final int DEFAULT_LINE_LENGTH_THRESHOLD = 500; // There are two separate CodeConsumers, one for pretty-printing and // another for compact printing. // There are two implementations because the CompactCodePrinter // potentially has a very different implementation to the pretty // version. private abstract static class MappedCodePrinter extends CodeConsumer { final private Deque<Mapping> mappings; final private List<Mapping> allMappings; final private boolean createSrcMap; final private SourceMap.DetailLevel sourceMapDetailLevel; protected final StringBuilder code = new StringBuilder(1024); protected final int lineLengthThreshold; protected int lineLength = 0; protected int lineIndex = 0; MappedCodePrinter( int lineLengthThreshold, boolean createSrcMap, SourceMap.DetailLevel sourceMapDetailLevel) { Preconditions.checkState(sourceMapDetailLevel != null); this.lineLengthThreshold = lineLengthThreshold; this.createSrcMap = createSrcMap; this.sourceMapDetailLevel = sourceMapDetailLevel; this.mappings = createSrcMap ? new ArrayDeque<Mapping>() : null; this.allMappings = createSrcMap ? new ArrayList<Mapping>() : null; } /** * Maintains a mapping from a given node to the position * in the source code at which its generated form was * placed. This position is relative only to the current * run of the CodeConsumer and will be normalized * later on by the SourceMap. * * @see SourceMap */ private static class Mapping { Node node; Position start; Position end; } /** * Starts the source mapping for the given * node

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> at the current position. */ @Override void startSourceMapping(Node node) { Preconditions.checkState(sourceMapDetailLevel != null); Preconditions.checkState(node != null); if (createSrcMap && node.getProp(Node.SOURCENAME_PROP) != null && node.getLineno() > 0 && sourceMapDetailLevel.apply(node)) { int line = getCurrentLineIndex(); int index = getCurrentCharIndex(); Preconditions.checkState(line >= 0); Mapping mapping = new Mapping(); mapping.node = node; mapping.start = new Position(line, index); mappings.push(mapping); allMappings.add(mapping); } } /** * Finishes the source mapping for the given * node at the current position. */ @Override void endSourceMapping(Node node) { if (createSrcMap && !mappings.isEmpty() && mappings.peek().node == node) { Mapping mapping = mappings.pop(); int line = getCurrentLineIndex(); int index = getCurrentCharIndex(); Preconditions.checkState(line >= 0); mapping.end = new Position(line, index); } } /** * Generates the source map from the given code consumer, * appending the information it saved to the SourceMap * object given. */ void generateSourceMap(SourceMap map){ if (createSrcMap) { for (Mapping mapping : allMappings) { map.addMapping(mapping.node, mapping.start, mapping.end); } } } /** * Reports to the code consumer that the given line has been cut at the * given position (i.e. a \n has been inserted there). All mappings in * the source maps after that position will be renormalized as needed. */ void reportLineCut(int lineIndex, int charIndex) { if (createSrcMap) { for (Mapping mapping : allMappings) { mapping.start = convertPosition(mapping.start, lineIndex, charIndex); if (mapping.end != null) { mapping.end = convertPosition(mapping.end, lineIndex, charIndex); } } } } /** * Converts the given position by normalizing it against the insertion * of a newline at the given line and character position. * * @param position The existing position before the newline was inserted. * @param lineIndex The index of the line at which the newline was inserted. * @param characterPosition The position on the line at which the newline * was inserted. * * @return The normalized position. */ private Position convertPosition(Position position, int lineIndex, int characterPosition) { int originalLine = position.getLineNumber(); int originalChar = position.getCharacterIndex(); if (originalLine == lineIndex && originalChar >= characterPosition) { // If the position falls on the line itself, then normalize it // if it falls at or

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> indent++; endLine(); } @Override void endCaseBody() { super.endCaseBody(); indent--; endStatement(); } @Override void appendOp(String op, boolean binOp) { if (binOp) { if (getLastChar() != ' ') { append(" "); } append(op); append(" "); } else { append(op); } } /** * If the body of a for loop or the then clause of an if statement has * a single statement, should it be wrapped in a block? * {@inheritDoc} */ @Override boolean shouldPreserveExtraBlocks() { // When pretty-printing, always place the statement in its own block // so it is printed on a separate line. This allows breakpoints to be // placed on the statement. return true; } /** * @return The TRY node for the specified CATCH node. */ private Node getTryForCatch(Node n) { return n.getParent().getParent(); } /** * @return Whether the a line break should be added after the specified * BLOCK. */ @Override boolean breakAfterBlockFor(Node n, boolean isStatementContext) { Preconditions.checkState(n.getType() == Token.BLOCK); Node parent = n.getParent(); if (parent != null) { int type = parent.getType(); switch (type) { case Token.DO: // Don't break before 'while' in DO-WHILE statements. return false; case Token.FUNCTION: // FUNCTIONs are handled separately, don't break here. return false; case Token.TRY: // Don't break before catch return n != parent.getFirstChild(); case Token.CATCH: // Don't break before finally return !NodeUtil.hasFinally(getTryForCatch(parent)); case Token.IF: // Don't break before else return n == parent.getLastChild(); } } return true; } } static class CompactCodePrinter extends MappedCodePrinter { // The CompactCodePrinter tries to emit just enough newlines to stop there // being lines longer than the threshold. Since the output is going to be // gzipped, it makes sense to try to make the newlines appear in similar // contexts so that GZIP can encode them for 'free'. // // This version tries to break the lines at 'preferred' places, which are // between the top-level forms. This works because top level forms tend to // be more uniform than arbitary legal contexts. Better compression would // probably require explicit modelling of the gzip algorithm. private final boolean lineBreak; private int lineStartPosition = 0; private int preferredBreakPosition = 0; /** * @param lineBreak break the lines a bit more aggressively * @param lineLengthThreshold The length of a line after which we force * a newline when possible.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> @param node The root node. */ Builder(Node node) { root = node; } /** * Sets whether pretty printing should be used. * @param prettyPrint If true, pretty printing will be used. */ Builder setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; return this; } /** * Sets whether line breaking should be done automatically. * @param lineBreak If true, line breaking is done automatically. */ Builder setLineBreak(boolean lineBreak) { this.lineBreak = lineBreak; return this; } /** * Sets whether to output closure-style type annotations. * @param outputTypes If true, outputs closure-style type annotations. */ Builder setOutputTypes(boolean outputTypes) { this.outputTypes = outputTypes; return this; } /** * Sets the line length threshold that will be used to determine * when to break lines, if line breaking is on. * * @param threshold The line length threshold. */ Builder setLineLengthThreshold(int threshold) { this.lineLengthThreshold = threshold; return this; } /** * Sets the source map to which to write the metadata about * the generated source code. * * @param sourceMap The source map. */ Builder setSourceMap(SourceMap sourceMap) { this.sourceMap = sourceMap; return this; } /** * @param level The detail level to use. */ Builder setSourceMapDetailLevel(SourceMap.DetailLevel level) { Preconditions.checkState(level != null); this.sourceMapDetailLevel = level; return this; } /** * Set the charset to use when determining what characters need to be * escaped in the output. */ Builder setOutputCharset(Charset outCharset) { this.outputCharset = outCharset; return this; } /** * Generates the source code and returns it. */ String build() { if (root == null) { throw new IllegalStateException( "Cannot build without root node being specified"); } Format outputFormat = outputTypes ? Format.TYPED : prettyPrint ? Format.PRETTY : Format.COMPACT; return toSource(root, outputFormat, lineBreak, lineLengthThreshold, sourceMap, sourceMapDetailLevel, outputCharset); } } enum Format { COMPACT, PRETTY, TYPED } /** * Converts a tree to js code */ private static String toSource(Node root, Format outputFormat, boolean lineBreak, int lineLengthThreshold, SourceMap sourceMap, SourceMap.DetailLevel sourceMapDetailLevel, Charset outputCharset) { Preconditions.checkState(sourceMapDetailLevel != null); boolean createSourceMap = (sourceMap != null); MappedCodePrinter mcp = outputFormat == Format.COMPACT ? new CompactCodePrinter( lineBreak,

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowEquality(p.typeB); } }; /** * Merging function for strict non-equality between types. */ private static final Function<TypePair, TypePair> SHNE = new Function<TypePair, TypePair>() { public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowInequality(p.typeB); } }; /** * Merging function for inequality comparisons between types. */ private final Function<TypePair, TypePair> INEQ = new Function<TypePair, TypePair>() { public TypePair apply(TypePair p) { return new TypePair( getRestrictedWithoutUndefined(p.typeA), getRestrictedWithoutUndefined(p.typeB)); } }; /** * Creates a semantic reverse abstract interpreter. */ SemanticReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { super(convention, typeRegistry); } public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { // Check for the typeof operator. int operatorToken = condition.getType(); switch (operatorToken) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) { typeOfNode = left; stringNode = right; } else if (right.getType() == Token.TYPEOF && left.getType() == Token.STRING) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); } } } switch (operatorToken) { case Token.AND: if (outcome

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } case Token.EQ: if (outcome) { return caseEquality(condition, blindScope, EQ); } else { return caseEquality(condition, blindScope, NE); } case Token.NE: if (outcome) { return caseEquality(condition, blindScope, NE); } else { return caseEquality(condition, blindScope, EQ); } case Token.SHEQ: if (outcome) { return caseEquality(condition, blindScope, SHEQ); } else { return caseEquality(condition, blindScope, SHNE); } case Token.SHNE: if (outcome) { return caseEquality(condition, blindScope, SHNE); } else { return caseEquality(condition, blindScope, SHEQ); } case Token.NAME: case Token.GETPROP: return caseNameOrGetProp(condition, blindScope, outcome); case Token.ASSIGN: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild().getNext(), blindScope, outcome), outcome); case Token.NOT: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), blindScope, !outcome); case Token.LE: case Token.LT: case Token.GE: case Token.GT: if (outcome) { return caseEquality(condition, blindScope, INEQ); } break; case Token.INSTANCEOF: return caseInstanceOf( condition.getFirstChild(), condition.getLastChild(), blindScope, outcome); case Token.IN: if (outcome && condition.getFirstChild().getType() == Token.STRING) { return caseIn(condition.getLastChild(), condition.getFirstChild().getString(), blindScope); } break; case Token.CASE: Node left = condition.getParent().getFirstChild(); // the switch condition Node right = condition.getFirstChild(); if (outcome) { return caseEquality(left, right, blindScope, SHEQ); } else { return caseEquality(left, right, blindScope, SHNE); } } return nextPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } private FlowScope caseEquality(Node condition, FlowScope blindScope, Function<TypePair, TypePair> merging) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ToBooleanOutcome(condition); // creating new scope if ((leftType != null && leftIsRefineable) || (rightType != null && rightIsRefineable)) { FlowScope informed = blindScope.createChildFlowScope(); if (leftIsRefineable && leftType != null) { declareNameInScope(informed, left, leftType); } if (rightIsRefineable && rightType != null) { declareNameInScope(informed, right, rightType); } return informed; } } return blindScope; } private FlowScope caseAndOrMaybeShortCircuiting(Node left, Node right, FlowScope blindScope, boolean condition) { FlowScope leftScope = firstPreciserScopeKnowingConditionOutcome( left, blindScope, !condition); StaticSlot<JSType> leftVar = leftScope.findUniqueRefinedSlot(blindScope); if (leftVar == null) { return blindScope; } FlowScope rightScope = firstPreciserScopeKnowingConditionOutcome( left, blindScope, condition); rightScope = firstPreciserScopeKnowingConditionOutcome( right, rightScope, !condition); StaticSlot<JSType> rightVar = rightScope.findUniqueRefinedSlot(blindScope); if (rightVar == null || !leftVar.getName().equals(rightVar.getName())) { return blindScope; } JSType type = leftVar.getType().getLeastSupertype(rightVar.getType()); FlowScope informed = blindScope.createChildFlowScope(); informed.inferSlotType(leftVar.getName(), type); return informed; } private FlowScope caseNameOrGetProp(Node name, FlowScope blindScope, boolean outcome) { JSType type = getTypeIfRefinable(name, blindScope); if (type != null) { JSType restrictedType = type.getRestrictedTypeGivenToBooleanOutcome(outcome); FlowScope informed = blindScope.createChildFlowScope(); declareNameInScope(informed, name, restrictedType); return informed; } return blindScope; } private FlowScope caseTypeOf(Node node, JSType type, String value, boolean resultEqualsValue, FlowScope blindScope) { JSType restrictedType = getRestrictedByTypeOfResult(type, value, resultEqualsValue); if (restrictedType == null) { return blindScope; } FlowScope informed = blindScope.createChildFlowScope(); declareNameInScope(informed, node, restrictedType); return informed; } private FlowScope caseInstanceOf(Node left, Node right, FlowScope blindScope, boolean outcome) { JSType leftType = getTypeIfRefinable(left, blindScope); if (leftType == null) { return blindScope; } JSType rightType = right.getJSType(); ObjectType targetType = typeRegistry.getNativeObjectType(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { this.compiler = compiler; } /** * Iterate through the nodes, renaming all the labels. */ class ProcessLabels implements ScopedCallback { ProcessLabels() { // Create a entry for global scope. namespaceStack.push(new LabelNamespace()); } // A stack of labels namespaces. Labels in an outer scope aren't part of an // inner scope, so a new namespace is created each time a scope is entered. final Deque<LabelNamespace> namespaceStack = Lists.newLinkedList(); // NameGenerator is used to create safe label names. final NameGenerator nameGenerator = new NameGenerator(new HashSet<String>(), "", null); // The list of generated names. Typically, the first name will be "a", // the second "b", etc. final ArrayList<String> names = new ArrayList<String>(); @Override public void enterScope(NodeTraversal nodeTraversal) { // Start a new namespace for label names. namespaceStack.push(new LabelNamespace()); } @Override public void exitScope(NodeTraversal nodeTraversal) { namespaceStack.pop(); } /** * shouldTraverse is call when descending into the Node tree, so it is used * here to build the context for label renames. * * {@inheritDoc} */ public boolean shouldTraverse(NodeTraversal nodeTraversal, Node node, Node parent) { if (node.getType() == Token.LABEL) { // Determine the new name for this label. LabelNamespace current = namespaceStack.peek(); int currentDepth = current.renameMap.size() + 1; String name = node.getFirstChild().getString(); // Store the context for this label name. LabelInfo li = new LabelInfo(currentDepth); Preconditions.checkState(!current.renameMap.containsKey(name)); current.renameMap.put(name, li); // Create a new name, if needed, for this depth. if (names.size() < currentDepth) { names.add(nameGenerator.generateNextName()); } String newName = getNameForId(currentDepth); compiler.addToDebugLog("label renamed: " + name + " => " + newName); } return true; } /** * Delegate the actual processing of the node to visitLabel and * visitBreakOrContinue. * * {@inheritDoc} */ public void visit(NodeTraversal nodeTraversal, Node node, Node parent) { switch (node.getType()) { case Token.LABEL: visitLabel(node, parent); break; case Token.BREAK: case Token.CONTINUE: visitBreakOrContinue(node); break; } } /** * Rename label references in breaks and continues. * @param node The break or continue node. */ private void visitBreakOrContinue(Node node) { Node nameNode = node.getFirstChild(); if (nameNode != null) { // This is a named break or continue; String name = nameNode.getString();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Preconditions.checkState(name.length() != 0); LabelInfo li = getLabelInfo(name); if (li != null) { String newName = getNameForId(li.id); // Mark the label as referenced so it isn't removed. li.referenced = true; if (!name.equals(newName)) { // Give it the short name. nameNode.setString(newName); compiler.reportCodeChange(); } } } } /** * Rename or remove labels. * @param node The label node. * @param parent The parent of the label node. */ private void visitLabel(Node node, Node parent) { Node nameNode = node.getFirstChild(); Preconditions.checkState(nameNode != null); String name = nameNode.getString(); LabelInfo li = getLabelInfo(name); // This is a label... if (li.referenced) { String newName = getNameForId(li.id); if (!name.equals(newName)) { // ... and it is used, give it the short name. nameNode.setString(newName); compiler.reportCodeChange(); } } else { // ... and it is not referenced, just remove it. Node newChild = node.getLastChild(); node.removeChild(newChild); parent.replaceChild(node, newChild); if (newChild.getType() == Token.BLOCK) { NodeUtil.tryMergeBlock(newChild); } compiler.reportCodeChange(); } // Remove the label from the current stack of labels. namespaceStack.peek().renameMap.remove(name); } /** * @param id The id, which is the depth of the label in the current context, * for which to get a short name. * @return The short name of the identified label. */ String getNameForId(int id) { return names.get(id - 1); } /** * @param name The name to retrieve information about. * @return The structure representing the name in the current context. */ LabelInfo getLabelInfo(String name) { return namespaceStack.peek().renameMap.get(name); } } @Override public void process(Node externs, Node root) { // Do variable reference counting. NodeTraversal.traverse(compiler, root, new ProcessLabels()); } private static class LabelInfo { boolean referenced = false; final int id; LabelInfo(int id) { this.id = id; } } private static class LabelNamespace { final Map<String, LabelInfo> renameMap = new HashMap<String, LabelInfo>(); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Checks for non side effecting statements such as * <pre> * var s = "this string is " * "continued on the next line but you forgot the +"; * x == foo(); // should that be '='? * foo();; // probably just a stray-semicolon. Doesn't hurt to check though * </p> * and generates warnings. * */ final class CheckSideEffects extends AbstractPostOrderCallback { static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning( "JSC_USELESS_CODE", "Suspicious code. {0}"); private final CheckLevel level; CheckSideEffects(CheckLevel level) { this.level = level; } public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.getType() == Token.EMPTY || n.getType() == Token.COMMA) { return; } if (parent == null) return; int pt = parent.getType(); if (pt == Token.COMMA) { Node gramps = parent.getParent(); if (gramps.getType() == Token.CALL && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first // expression to a comma to be a no-op if it's used to indirect // an eval. if (n == parent.getFirstChild() && parent.getChildCount() == 2 && n.getNext().getType() == Token.NAME && "eval".equals(n.getNext().getString())) { return

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; } } if (n == parent.getLastChild()) { for (Node an : parent.getAncestors()) { int ancestorType = an.getType(); if (ancestorType == Token.COMMA) continue; if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return; else break; } } } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) { if (pt == Token.FOR && parent.getChildCount() == 4 && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) { // Fall through and look for warnings for the 1st and 3rd child // of a for. } else { return; // it might be ok to not have a side-effect } } if (NodeUtil.isSimpleOperatorType(n.getType()) || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) { if (n.isQualifiedName() && n.getJSDocInfo() != null) { // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. return; } else if (NodeUtil.isExpressionNode(n)) { // we already reported the problem when we visited the child. return; } String msg = "This code lacks side-effects. Is there a bug?"; if (n.getType() == Token.STRING) { msg = "Is there a missing '+' on the previous line?"; } t.getCompiler().report( t.makeError(n, level, USELESS_CODE_ERROR, msg)); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; /** * Control whether warnings should be restricted or suppressed for specified * paths. * * @author anatol@google.com (Anatol Pomazau) */ public class ShowByPathWarningsGuard extends WarningsGuard { /** * Controls whether warnings should be restricted to a specified path or * suppressed within the specified path. */ public enum ShowType { INCLUDE, // Suppress warnings outside the path. EXCLUDE; // Suppress warnings within the path. } private final String[] paths; private final ShowType showType; public ShowByPathWarningsGuard(String checkWarningsOnlyForPath) { this(checkWarningsOnlyForPath, ShowType.INCLUDE); } public ShowByPathWarningsGuard(String[] checkWarningsOnlyForPath) { this(checkWarningsOnlyForPath, ShowType.INCLUDE); } public ShowByPathWarningsGuard(String path, ShowType showType) { this(new String[] { path }, showType); } public ShowByPathWarningsGuard(String[] paths, ShowType showType) { Preconditions.checkArgument(paths != null); Preconditions.checkArgument(showType != null); this.paths = paths; this.showType = showType; } @Override public CheckLevel level(JSError error) { final String errorPath = error.sourceName; if (error.level != CheckLevel.ERROR && errorPath != null) { boolean inPath = false; for (String path : paths) { inPath |= errorPath.contains(path); } if (inPath ^ (showType == ShowType.INCLUDE)) { return CheckLevel.OFF; } } return null; } @Override protected int getPriority() { return WarningsGuard.Priority.FILTER_BY_PATH.value; // applied first } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Get the phase ordering of loops during this run. * Returns an empty list when the loops are not randomized. */ static List<List<String>> getLoopsRun() { return loopsRun; } /** * Clears the phase ordering of loops during this run. */ static void clearLoopsRun() { loopsRun.clear(); } /** * Add the passes generated by the given factories to the compile sequence. * * Automatically pulls multi-run passes into fixed point loops. If there * are 2 or more multi-run passes in a row, they will run together in * the same fixed point loop. If A and B are in the same fixed point loop, * the loop will continue to run both A and B until both are finished * making changes. * * Other than that, the PhaseOptimizer is free to tweak the order and * frequency of multi-run passes in a fixed-point loop. */ void consume(List<PassFactory> factories) { Loop currentLoop = new LoopInternal(); boolean isCurrentLoopPopulated = false; for (PassFactory factory : factories) { if (factory.isOneTimePass()) { if (isCurrentLoopPopulated) { passes.add(currentLoop); currentLoop = new LoopInternal(); isCurrentLoopPopulated = false; } addOneTimePass(factory); } else { currentLoop.addLoopedPass(factory); isCurrentLoopPopulated = true; } } if (isCurrentLoopPopulated) { passes.add(currentLoop); } } /** * Add the pass generated by the given factory to the compile sequence. * This pass will be run once. */ void addOneTimePass(PassFactory factory) { passes.add(new PassFactoryDelegate(compiler, factory)); } /** * Add a loop to the compile sequence. This loop will continue running * until the AST stops changing. * @return The loop structure. Pass suppliers should be added to the loop. */ Loop addFixedPointLoop() { Loop loop = new LoopInternal(); passes.add(loop); return loop; } /** * Adds a sanity checker to be run after every pass. Intended for development. */ void setSanityCheck(PassFactory sanityCheck) { this.sanityCheck = sanityCheck; } /** * Run all the passes in the optimizer. */ public void process(Node externs, Node root) { for (CompilerPass pass : passes) { pass.process(externs, root); if (hasHaltingErrors()) { return; } } } /** * Marks the beginning of a pass. */ private void startPass(String passName) { Preconditions.checkState(currentTracer == null && currentPassName == null); currentPassName = passName; currentTracer = newTracer(passName); } /** * Marks

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> the end of a pass. */ private void endPass(Node externs, Node root) { Preconditions.checkState(currentTracer != null && currentPassName != null); String passToCheck = currentPassName; try { stopTracer(currentTracer, currentPassName); currentPassName = null; currentTracer = null; maybeSanityCheck(externs, root); } catch (Exception e) { // TODO(johnlenz): Remove this once the normalization checks report // errors instead of exceptions. throw new RuntimeException("Sanity check failed for " + passToCheck, e); } } /** * Runs the sanity check if it is available. */ void maybeSanityCheck(Node externs, Node root) { if (sanityCheck != null) { sanityCheck.create(compiler).process(externs, root); } } private boolean hasHaltingErrors() { return compiler.hasHaltingErrors(); } /** * Returns a new tracer for the given pass name. */ private Tracer newTracer(String passName) { String comment = passName + (recentChange.hasCodeChanged() ? " on recently changed AST" : ""); if (tracker != null) { tracker.recordPassStart(passName); } return new Tracer("JSCompiler", comment); } private void stopTracer(Tracer t, String passName) { long result = t.stop(); if (tracker != null) { tracker.recordPassStop(passName, result); } } /** * A single compiler pass. */ private abstract class NamedPass implements CompilerPass { private final String name; NamedPass(String name) { this.name = name; } public void process(Node externs, Node root) { logger.info(name); startPass(name); processInternal(externs, root); endPass(externs, root); } abstract void processInternal(Node externs, Node root); } /** * Delegates to a PassFactory for processing. */ private class PassFactoryDelegate extends NamedPass { private final AbstractCompiler myCompiler; private final PassFactory factory; private PassFactoryDelegate( AbstractCompiler myCompiler, PassFactory factory) { super(factory.getName()); this.myCompiler = myCompiler; this.factory = factory; } @Override void processInternal(Node externs, Node root) { factory.create(myCompiler).process(externs, root); } } /** * Runs a set of compiler passes until they reach a fixed point. */ static abstract class Loop implements CompilerPass { abstract void addLoopedPass(PassFactory factory); } /** * Runs a set of compiler passes until they reach a fixed point. * * Notice that this is a non-static class, because it includes the closure * of PhaseOptimizer. */ private class LoopInternal extends Loop { private final List<

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>NamedPass> myPasses = Lists.newArrayList(); private final Set<String> myNames = Sets.newHashSet(); @Override void addLoopedPass(PassFactory factory) { String name = factory.getName(); Preconditions.checkArgument( !myNames.contains(name), "Already a pass with name '" + name + "' in this loop"); myNames.add(factory.getName()); myPasses.add(new PassFactoryDelegate(compiler, factory)); } /** * Gets the pass names, in order. */ private List<String> getPassOrder() { List<String> order = Lists.newArrayList(); for (NamedPass pass : myPasses) { order.add(pass.name); } return order; } public void process(Node externs, Node root) { Preconditions.checkState(!loopMutex, "Nested loops are forbidden"); loopMutex = true; if (randomizeLoops) { randomizePasses(); } else { optimizePasses(); } try { // TODO(nicksantos): Use a smarter algorithm that dynamically adjusts // the order that passes are run in. int count = 0; out: do { if (count++ > MAX_LOOPS) { compiler.throwInternalError(OPTIMIZE_LOOP_ERROR, null); } recentChange.reset(); // reset before this round of optimizations for (CompilerPass pass : myPasses) { pass.process(externs, root); if (hasHaltingErrors()) { break out; } } } while (recentChange.hasCodeChanged() && !hasHaltingErrors()); if (randomizeLoops) { loopsRun.add(getPassOrder()); } } finally { loopMutex = false; } } /** Re-arrange the passes in a random order. */ private void randomizePasses() { List<NamedPass> mixedupPasses = Lists.newArrayList(); Random random = new Random(); while (myPasses.size() > 0) { mixedupPasses.add( myPasses.remove(random.nextInt(myPasses.size()))); } myPasses.addAll(mixedupPasses); } /** Re-arrange the passes in an optimal order. */ private void optimizePasses() { // It's important that this ordering is deterministic, so that // multiple compiles with the same input produce exactly the same // results. // // To do this, grab any passes we recognize, and move them to the end // in an "optimal" order. List<NamedPass> optimalPasses = Lists.newArrayList(); for (String passName : OPTIMAL_ORDER) { for (NamedPass pass : myPasses) { if (pass.name.equals(passName)) { optimalPasses.add(pass); break; } } } myPasses.removeAll(optimalP

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>N, E>> nodeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : dNode.getInEdges()) { nodeList.add(edge.getSource()); } return nodeList; } @Override public List<DiGraphNode<N, E>> getDirectedSuccNodes( DiGraphNode<N, E> dNode) { if (dNode == null) { throw new IllegalArgumentException(dNode + " is null"); } List<DiGraphNode<N, E>> nodeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : dNode.getOutEdges()) { nodeList.add(edge.getDestination()); } return nodeList; } @Override public List<GraphvizEdge> getGraphvizEdges() { List<GraphvizEdge> edgeList = Lists.newArrayList(); for (LinkedDirectedGraphNode<N, E> node : nodes.values()) { for (DiGraphEdge<N, E> edge : node.getOutEdges()) { edgeList.add((LinkedDirectedGraphEdge<N, E>) edge); } } return edgeList; } @Override public List<GraphvizNode> getGraphvizNodes() { List<GraphvizNode> nodeList = Lists.newArrayListWithCapacity(nodes.size()); for (LinkedDirectedGraphNode<N, E> node : nodes.values()) { nodeList.add(node); } return nodeList; } @Override public String getName() { return "LinkedGraph"; } @Override public boolean isDirected() { return true; } @Override public Collection<GraphNode<N, E>> getNodes() { return Collections.<GraphNode<N, E>>unmodifiableCollection(nodes.values()); } @Override public List<GraphNode<N, E>> getNeighborNodes(N value) { DiGraphNode<N, E> node = getDirectedGraphNode(value); return getNeighborNodes(node); } public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) { List<GraphNode<N, E>> result = Lists.newArrayList(); for (Iterator<GraphNode<N, E>> i = ((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) { result.add(i.next()); } return result; } @Override public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) { LinkedDirectedGraphNode<N, E> node = nodes.get(value); Preconditions.checkNotNull(node); return node.neighborIterator(); } @Override public List<GraphEdge<N, E>> getEdges() { List<GraphEdge<N, E>> result = Lists.newArrayList(); for (DiGraphNode<N, E> node : nodes.values

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private final FunctionInjector injector; private final boolean blockFunctionInliningEnabled; private final boolean inlineGlobalFunctions; private final boolean inlineLocalFunctions; private SpecializeModule.SpecializationState specializationState; InlineFunctions(AbstractCompiler compiler, Supplier<String> safeNameIdSupplier, boolean inlineGlobalFunctions, boolean inlineLocalFunctions, boolean blockFunctionInliningEnabled) { Preconditions.checkArgument(compiler != null); Preconditions.checkArgument(safeNameIdSupplier != null); this.compiler = compiler; this.inlineGlobalFunctions = inlineGlobalFunctions; this.inlineLocalFunctions = inlineLocalFunctions; this.blockFunctionInliningEnabled = blockFunctionInliningEnabled; this.injector = new FunctionInjector(compiler, safeNameIdSupplier, true); } FunctionState getOrCreateFunctionState(String fnName) { FunctionState fs = fns.get(fnName); if (fs == null) { fs = new FunctionState(); fns.put(fnName, fs); } return fs; } public void enableSpecialization(SpecializeModule.SpecializationState specializationState) { this.specializationState = specializationState; } @Override public void process(Node externs, Node root) { Preconditions.checkState(compiler.isNormalized()); NodeTraversal.traverse(compiler, root, new FindCandidateFunctions()); if (fns.isEmpty()) { return; // Nothing left to do. } NodeTraversal.traverse(compiler, root, new FindCandidatesReferences(fns, anonFns)); trimCanidatesNotMeetingMinimumRequirements(); if (fns.isEmpty()) { return; // Nothing left to do. } // Store the set of function names eligible for inlining and use this to // prevent function names from being moved into temporaries during // expression decomposition. If this movement were allowed it would prevent // the Inline callback from finding the function calls. // // This pass already assumes these are constants, so this is safe for anyone // using function inlining. // Set<String> fnNames = Sets.newHashSet(fns.keySet()); injector.setKnownConstants(fnNames); trimCanidatesUsingOnCost(); if (fns.isEmpty()) { return; // Nothing left to do. } resolveInlineConflicts(); decomposeExpressions(fnNames); NodeTraversal.traverse(compiler, root, new CallVisitor( fns, anonFns, new Inline(injector, specializationState))); removeInlinedFunctions(); } /** * Find functions that might be inlined. */ private class FindCandidateFunctions implements Callback { private int callsSeen = 0; @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { // Don't traverse into function bodies // if we aren't inlining local functions. return inlineLocalFunctions || nodeTraversal.inGlobalScope(); } public void visit(NodeTraversal t, Node n, Node parent)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { if ((t.inGlobalScope() && inlineGlobalFunctions) || (!t.inGlobalScope() && inlineLocalFunctions)) { findNamedFunctions(t, n, parent); findFunctionExpressions(t, n); } } public void findNamedFunctions(NodeTraversal t, Node n, Node parent) { if (!NodeUtil.isStatement(n)) { // There aren't any interesting functions here. return; } switch (n.getType()) { // Functions expressions in the form of: // var fooFn = function(x) { return ... } case Token.VAR: Preconditions.checkState(n.hasOneChild()); Node nameNode = n.getFirstChild(); if (nameNode.getType() == Token.NAME && nameNode.hasChildren() && nameNode.getFirstChild().getType() == Token.FUNCTION) { maybeAddFunction(new FunctionVar(n), t.getModule()); } break; // Named functions // function Foo(x) { return ... } case Token.FUNCTION: Preconditions.checkState(NodeUtil.isStatementBlock(parent) || parent.getType() == Token.LABEL); if (!NodeUtil.isFunctionExpression(n)) { Function fn = new NamedFunction(n); maybeAddFunction(fn, t.getModule()); } break; } } /** * Find function expressions that are called directly in the form of * (function(a,b,...){...})(a,b,...) * or * (function(a,b,...){...}).call(this,a,b, ...) */ public void findFunctionExpressions(NodeTraversal t, Node n) { switch (n.getType()) { // Functions expressions in the form of: // (function(){})(); case Token.CALL: Node fnNode = null; if (n.getFirstChild().getType() == Token.FUNCTION) { fnNode = n.getFirstChild(); } else if (NodeUtil.isFunctionObjectCall(n)) { Node fnIdentifingNode = n.getFirstChild().getFirstChild(); if (fnIdentifingNode.getType() == Token.FUNCTION) { fnNode = fnIdentifingNode; } } // If a interesting function was discovered, add it. if (fnNode != null) { Function fn = new FunctionExpression(fnNode, callsSeen++); maybeAddFunction(fn, t.getModule()); anonFns.put(fnNode, fn.getName()); } break; } } } /** * Updates the FunctionState object for the given function. Checks if the * given function matches the criteria for an inlinable function. */ private void maybeAddFunction(Function fn, JSModule module) { String name = fn.getName(); FunctionState fs = getOrCreateFunctionState(name); // TODO(johnlenz): Maybe "smarten" FunctionState by adding this logic // to it? // If the function

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Don't inline exported functions. String fnName = fn.getName(); if (compiler.getCodingConvention().isExported(fnName)) { // TODO(johnlenz): Should we allow internal references to be inlined? // An exported name can be replaced externally, any inlined instance // would not reflect this change. // To allow inlining we need to be able to distinguish between exports // that are used in a read-only fashion and those that can be replaced // by external definitions. return false; } // Don't inline this special function if (RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(fnName)) { return false; } // Don't inline if we are specializing and the function can't be fixed up if (specializationState != null && !specializationState.canFixupFunction(fn.getFunctionNode())) { return false; } Node fnNode = fn.getFunctionNode(); return injector.doesFunctionMeetMinimumRequirements(fnName, fnNode); } /** * @see CallVisitor */ private interface CallVisitorCallback { public void visitCallSite( NodeTraversal t, Node callNode, Node parent, FunctionState fs); } /** * Visit call sites for functions in functionMap. */ private static class CallVisitor extends AbstractPostOrderCallback { protected CallVisitorCallback callback; private Map<String, FunctionState> functionMap; private Map<Node, String> anonFunctionMap; CallVisitor(Map<String, FunctionState> fns, Map<Node, String> anonFns, CallVisitorCallback callback) { this.functionMap = fns; this.anonFunctionMap = anonFns; this.callback = callback; } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { // Function calls case Token.CALL: Node child = n.getFirstChild(); String name = null; // NOTE: The normalization pass insures that local names do not // collide with global names. if (child.getType() == Token.NAME) { name = child.getString(); } else if (child.getType() == Token.FUNCTION) { name = anonFunctionMap.get(child); } else if (NodeUtil.isFunctionObjectCall(n)) { Preconditions.checkState(NodeUtil.isGet(child)); Node fnIdentifingNode = child.getFirstChild(); if (fnIdentifingNode.getType() == Token.NAME) { name = fnIdentifingNode.getString(); } else if (fnIdentifingNode.getType() == Token.FUNCTION) { name = anonFunctionMap.get(fnIdentifingNode); } } if (name != null) { FunctionState fs = functionMap.get(name); // Only visit call-sites for functions that can be inlined. if (fs != null) { callback.visit

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>CallSite(t, n, parent, fs); } } break; } } } /** * Find references to functions that are inlinable. */ private class FindCandidatesReferences extends CallVisitor implements CallVisitorCallback { FindCandidatesReferences( Map<String, FunctionState> fns, Map<Node, String> anonFns) { super(fns, anonFns, null); this.callback = this; } @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); if (n.getType() == Token.NAME) { checkNameUsage(t, n, parent); } } public void visitCallSite( NodeTraversal t, Node callNode, Node parent, FunctionState fs) { maybeAddReference(t, fs, callNode, t.getModule()); } void maybeAddReference(NodeTraversal t, FunctionState fs, Node callNode, JSModule module) { if (!fs.canInline()) { return; } boolean referenceAdded = false; InliningMode mode = fs.canInlineDirectly() ? InliningMode.DIRECT : InliningMode.BLOCK; referenceAdded = maybeAddReferenceUsingMode( t, fs, callNode, module, mode); if (!referenceAdded && mode == InliningMode.DIRECT && blockFunctionInliningEnabled) { // This reference can not be directly inlined, see if // block replacement inlining is possible. mode = InliningMode.BLOCK; referenceAdded = maybeAddReferenceUsingMode( t, fs, callNode, module, mode); } if (!referenceAdded) { // Don't try to remove a function if we can't inline all // the references. fs.setRemove(false); } } private boolean maybeAddReferenceUsingMode( NodeTraversal t, FunctionState fs, Node callNode, JSModule module, InliningMode mode) { if (specializationState != null) { // If we're specializing, make sure we can fixup // the containing function before inlining Node containingFunction = getContainingFunction(t); if (containingFunction != null && !specializationState.canFixupFunction( containingFunction)) { return false; } } CanInlineResult result = injector.canInlineReferenceToFunction( t, callNode, fs.getFn().getFunctionNode(), fs.getNamesToAlias(), mode, fs.getReferencesThis(), fs.hasInnerFunctions()); if (result != CanInlineResult.NO) { // Yeah! boolean decompose = (result == CanInlineResult.AFTER_DECOMPOSITION); fs.addReference(new Reference(callNode, module, mode, decompose)); return true; } return false; } /** * Find functions that can be inlined. */ private void checkNameUsage(NodeTraversal t, Node n, Node parent

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>) { Preconditions.checkState(n.getType() == Token.NAME); if (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION) { // This is a declaration. Duplicate declarations are handle during // function candidate gathering. return; } if (parent.getType() == Token.CALL && parent.getFirstChild() == n) { // This is a normal reference to the function. return; } // Check for a ".call" to the named function: // CALL // GETPROP/GETELEM // NAME // STRING == "call" // This-Value // Function-parameter-1 // ... if (NodeUtil.isGet(parent) && n == parent.getFirstChild() && n.getNext().getType() == Token.STRING && n.getNext().getString().equals("call")) { Node gramps = n.getAncestor(2); if (gramps.getType() == Token.CALL && gramps.getFirstChild() == parent) { // Yep, a ".call". return; } } // Other refs to a function name remove its candidacy for inlining String name = n.getString(); FunctionState fs = fns.get(name); if (fs == null) { return; } // If the name is being assigned to it can not be inlined. if (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) { // e.g. bar = something; <== we can't inline "bar" // so mark the function as uninlinable. // TODO(johnlenz): Should we just remove it from fns here? fs.setInline(false); } else { // e.g. var fn = bar; <== we can't inline "bar" // As this reference can't be inlined mark the function as // unremovable. fs.setRemove(false); } } } /** * Inline functions at the call sites. */ private static class Inline implements CallVisitorCallback { private final FunctionInjector injector; private final SpecializeModule.SpecializationState specializationState; Inline(FunctionInjector injector, SpecializeModule.SpecializationState specializationState) { this.injector = injector; this.specializationState = specializationState; } public void visitCallSite( NodeTraversal t, Node callNode, Node parent, FunctionState fs) { Preconditions.checkState(fs.hasExistingFunctionDefinition()); if (fs.canInline()) { Reference ref = fs.getReference(callNode); // There are two cases ref can be null: if the call site was introduce // because it was part of a function that was inlined during this pass // or if the call site was trimmed from the list of references because // the function couldn't be inlined at this location. if (ref != null) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } return true; } /** * @return Whether inlining the function reduces code size. */ private boolean inliningLowersCost(FunctionState fs) { return injector.inliningLowersCost( fs.getModule(), fs.getFn().getFunctionNode(), fs.getReferences(), fs.getNamesToAlias(), fs.canRemove(), fs.getReferencesThis()); } /** * Size base inlining calculations are thrown off when a function that is * being inlined also contains calls to functions that are slated for * inlining. * * Specifically, a clone of the FUNCTION node tree is used when the function * is inlined. Calls in this new tree are not included in the list of function * references so they won't be inlined (which is what we want). Here we mark * those functions as non-removable (as they will have new references in the * cloned node trees). * * This prevents a function that would only be inlined because it is * referenced once from being inlined into multiple call sites because * the calling function has been inlined in multiple locations or the * function being removed while there are still references. */ private void resolveInlineConflicts() { for (FunctionState fs : fns.values()) { resolveInlineConflictsForFunction(fs); } } /** * @see #resolveInlineConflicts */ private void resolveInlineConflictsForFunction(FunctionState fs) { // Functions that aren't referenced don't cause conflicts. if (!fs.hasReferences()) { return; } Node fnNode = fs.getFn().getFunctionNode(); Set<String> names = findCalledFunctions(fnNode); if (!names.isEmpty()) { // Prevent the removal of the referenced functions. for (String name : names) { FunctionState fsCalled = fns.get(name); if (fsCalled != null && fsCalled.canRemove()) { fsCalled.setRemove(false); // For functions that can no longer be removed, check if they should // still be inlined. if (!mimimizeCost(fsCalled)) { // It can't be inlined remove it from the list. fsCalled.setInline(false); } } } // Make a copy of the Node, so it isn't changed by other inlines. fs.setSafeFnNode(fs.getFn().getFunctionNode().cloneTree()); } } /** * This functions that may be called directly. */ private Set<String> findCalledFunctions(Node node) { Set<String> changed = Sets.newHashSet(); findCalledFunctions(node, changed); return changed; } /** * @see #findCalledFunctions(Node) */ private void findCalledFunctions( Node node, Set<String> changed) { Preconditions.checkArgument(changed != null); // For each referenced function, add a new reference if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>node.getType() == Token.CALL) { Node child = node.getFirstChild(); if (child.getType() == Token.NAME) { String name = child.getString(); changed.add(name); } } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { findCalledFunctions(c, changed); } } /** * For any call-site that needs it, prepare the call-site for inlining * by rewriting the containing expression. */ private void decomposeExpressions(Set<String> fnNames) { ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), fnNames); for (FunctionState fs : fns.values()) { if (fs.canInline()) { for (Reference ref : fs.getReferences()) { if (ref.requiresDecomposition) { decomposer.maybeDecomposeExpression(ref.callNode); } } } } } /** * Removed inlined functions that no longer have any references. */ void removeInlinedFunctions() { for (FunctionState fs : fns.values()) { if (fs.canRemove()) { Function fn = fs.getFn(); Preconditions.checkState(fs.canInline()); Preconditions.checkState(fn != null); verifyAllReferencesInlined(fs); if (specializationState != null) { specializationState.reportRemovedFunction( fn.getFunctionNode(), fn.getDeclaringBlock()); } fn.remove(); compiler.reportCodeChange(); } } } /** * Sanity check to verify, that expression rewriting didn't * make a call inaccessible. */ void verifyAllReferencesInlined(FunctionState fs) { for (Reference ref : fs.getReferences()) { if (!ref.inlined) { throw new IllegalStateException("Call site missed.\n call: " + ref.callNode.toStringTree() + "\n parent: " + ref.callNode.getParent().toStringTree()); } } } /** * Use to track the decisions that have been make about a function. */ private static class FunctionState { private Function fn = null; private Node safeFnNode = null; private boolean inline = true; private boolean remove = true; private boolean inlineDirectly = false; private boolean referencesThis = false; private boolean hasInnerFunctions = false; private Map<Node, Reference> references = null; private JSModule module = null; private Set<String> namesToAlias = null; boolean hasExistingFunctionDefinition() { return (fn != null); } public void setReferencesThis(boolean referencesThis) { this.referencesThis = referencesThis; } public boolean getReferencesThis() { return this.referencesThis; } public void setHasInnerFunctions(boolean hasInnerFunctions) { this.hasInnerFunctions = hasInnerFunctions

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; } public boolean hasInnerFunctions() { return hasInnerFunctions; } void removeBlockInliningReferences() { Iterator<Entry<Node, Reference>> i; for (i = getReferencesInternal().entrySet().iterator(); i.hasNext();) { Entry<Node, Reference> entry = i.next(); if (entry.getValue().mode == InliningMode.BLOCK) { i.remove(); } } } public boolean hasBlockInliningReferences() { for (Reference r : getReferencesInternal().values()) { if (r.mode == InliningMode.BLOCK) { return true; } } return false; } public Function getFn() { return fn; } public void setFn(Function fn) { Preconditions.checkState(this.fn == null); this.fn = fn; } public Node getSafeFnNode() { return (safeFnNode != null) ? safeFnNode : fn.getFunctionNode(); } public void setSafeFnNode(Node safeFnNode) { this.safeFnNode = safeFnNode; } public boolean canInline() { return inline; } public void setInline(boolean inline) { this.inline = inline; if (inline == false) { // No need to keep references to function that can't be inlined. references = null; // Don't remove functions that we aren't inlining. remove = false; } } public boolean canRemove() { return remove; } public void setRemove(boolean remove) { this.remove = remove; } public boolean canInlineDirectly() { return inlineDirectly; } public void inlineDirectly(boolean directReplacement) { this.inlineDirectly = directReplacement; } public boolean hasReferences() { return (references != null && !references.isEmpty()); } private Map<Node, Reference> getReferencesInternal() { if (references == null) { return Collections.emptyMap(); } return references; } public void addReference(Reference ref) { if (references == null) { references = Maps.newHashMap(); } references.put(ref.callNode, ref); } public Collection<Reference> getReferences() { return getReferencesInternal().values(); } public Reference getReference(Node n) { return getReferencesInternal().get(n); } public Set<String> getNamesToAlias() { if (namesToAlias == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(namesToAlias); } public void setNamesToAlias(Set<String> names) { namesToAlias = names; } public void setModule(JSModule module) { this.module = module; } public JSModule getModule() { return module; } } /** * Interface for dealing with function declarations and function * expressions equally */ private

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.Sets; import com.google.javascript.jscomp.ExpressionDecomposer.DecompositionType; import com.google.javascript.jscomp.MakeDeclaredNamesUnique.ContextualRenamer; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Collection; import java.util.Map; import java.util.Set; /** * A set of utility functions that replaces CALL with a specified * FUNCTION body, replacing and aliasing function parameters as * necessary. * * @author johnlenz@google.com (John Lenz) */ class FunctionInjector { private final AbstractCompiler compiler; private final Supplier<String> safeNameIdSupplier; private final boolean allowDecomposition; private Set<String> knownConstants = Sets.newHashSet(); /** * @param allowDecomposition Whether an effort should be made to break down * expressions into simpler expressions to allow functions to be injected * where they would otherwise be disallowed. */ public FunctionInjector( AbstractCompiler compiler, Supplier<String> safeNameIdSupplier, boolean allowDecomposition) { Preconditions.checkNotNull(compiler); Preconditions.checkNotNull(safeNameIdSupplier); this.compiler = compiler; this.safeNameIdSupplier = safeNameIdSupplier; this.allowDecomposition = allowDecomposition; } /** The type of inlining to perform. */ enum InliningMode { /** * Directly replace the call expression. Only functions of meeting * strict preconditions can be inlined. */ DIRECT, /** * Replaces the call expression with a block of statements. Conditions * on the function are looser in mode, but stricter on the call site. */ BLOCK } /** Holds a reference to the call node of a function call */ static class Reference { final Node callNode; final JSModule module; final InliningMode mode; Reference(Node callNode, JSModule module, InliningMode mode){ this.callNode = callNode; this.module = module; this.mode = mode;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Introducing // an inner function into another function can capture a variable and cause // a memory leak. This isn't a problem in the global scope as those values // last until explicitly cleared. if (containsFunctions && !t.inGlobalScope()) { // TODO(johnlenz): Allow inlining into any scope without local names or // inner functions. return CanInlineResult.NO; } // TODO(johnlenz): Add support for 'apply' if (referencesThis && !NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Allow 'this' references to be replaced with a // global 'this' object. return CanInlineResult.NO; } if (mode == InliningMode.DIRECT) { return canInlineReferenceDirectly(callNode, fnNode); } else { return canInlineReferenceAsStatementBlock( t, callNode, fnNode, needAliases); } } /** * Only ".call" calls and direct calls to functions are supported. * @param callNode The call evaluate. * @return Whether the call is of a type that is supported. */ private boolean isSupportedCallType(Node callNode) { if (callNode.getFirstChild().getType() != Token.NAME) { if (NodeUtil.isFunctionObjectCall(callNode)) { Node thisValue = callNode.getFirstChild().getNext(); if (thisValue == null || thisValue.getType() != Token.THIS) { return false; } } else if (NodeUtil.isFunctionObjectApply(callNode)) { return false; } } return true; } /** * Inline a function into the call site. */ Node inline( NodeTraversal t, Node callNode, String fnName, Node fnNode, InliningMode mode) { Preconditions.checkState(compiler.isNormalized()); if (mode == InliningMode.DIRECT) { return inlineReturnValue(callNode, fnNode); } else { return inlineFunction(callNode, fnNode, fnName); } } /** * Inline a function that fulfills the requirements of * canInlineReferenceDirectly into the call site, replacing only the CALL * node. */ private Node inlineReturnValue(Node callNode, Node fnNode) { Node block = fnNode.getLastChild(); Node callParentNode = callNode.getParent(); // NOTE: As the normalize pass guarantees globals aren't being // shadowed and an expression can't introduce new names, there is // no need to check for conflicts. // Create an argName -> expression map, checking for side effects. Map<String, Node> argMap = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); Node newExpression; if (!block.hasChildren()) { Node srcLocation = block; newExpression = NodeUtil.newUndefinedNode(srcLocation); } else { Node returnNode =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> block.getFirstChild(); Preconditions.checkArgument(returnNode.getType() == Token.RETURN); // Clone the return node first. Node safeReturnNode = returnNode.cloneTree(); Node inlineResult = FunctionArgumentInjector.inject( safeReturnNode, null, argMap); Preconditions.checkArgument(safeReturnNode == inlineResult); newExpression = safeReturnNode.removeFirstChild(); } callParentNode.replaceChild(callNode, newExpression); return newExpression; } /** * Supported call site types. */ private enum CallSiteType { /** * Used for a call site for which there does not exist a method * to inline it. */ UNSUPPORTED, /** * A call as a statement. For example: "foo();". * EXPR_RESULT * CALL */ SIMPLE_CALL, /** * An assignment, where the result of the call is assigned to a simple * name. For example: "a = foo();". * EXPR_RESULT * NAME A * CALL * FOO */ SIMPLE_ASSIGNMENT, /** * An var declaration and initialization, where the result of the call is * assigned to the declared name * name. For example: "a = foo();". * VAR * NAME A * CALL * FOO */ VAR_DECL_SIMPLE_ASSIGNMENT, /** * An arbitrary expression, the root of which is a EXPR_RESULT, IF, * RETURN, SWITCH or VAR. The call must be the first side-effect in * the expression. * * Examples include: * "if (foo()) {..." * "return foo();" * "var a = 1 + foo();" * "a = 1 + foo()" * "foo() ? 1:0" * "foo() && x" */ EXPRESSION, /** * An arbitrary expression, the root of which is a EXPR_RESULT, IF, * RETURN, SWITCH or VAR. Where the call is not the first side-effect in * the expression. */ DECOMPOSABLE_EXPRESSION, } /** * Determine which, if any, of the supported types the call site is. */ private CallSiteType classifyCallSite(Node callNode) { Node parent = callNode.getParent(); Node grandParent = parent.getParent(); // Verify the call site: if (NodeUtil.isExprCall(parent)) { // This is a simple call? Example: "foo();". return CallSiteType.SIMPLE_CALL; } else if (NodeUtil.isExprAssign(grandParent) && !NodeUtil.isLhs(callNode, parent) && parent.getFirstChild().getType() == Token.NAME && !NodeUtil.isConstantName(parent.getFirstChild())) { // This is a simple

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> assignment. Example: "x = foo();" return CallSiteType.SIMPLE_ASSIGNMENT; } else if (parent.getType() == Token.NAME && !NodeUtil.isConstantName(parent) && grandParent.getType() == Token.VAR && grandParent.hasOneChild()) { // This is a var declaration. Example: "var x = foo();" // TODO(johnlenz): Should we be checking for constants on the // left-hand-side of the assignments (and handling them as EXPRESSION? return CallSiteType.VAR_DECL_SIMPLE_ASSIGNMENT; } else { Node expressionRoot = ExpressionDecomposer.findExpressionRoot(callNode); if (expressionRoot != null) { ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, safeNameIdSupplier, knownConstants); DecompositionType type = decomposer.canExposeExpression( callNode); if (type == DecompositionType.MOVABLE) { return CallSiteType.EXPRESSION; } else if (type == DecompositionType.DECOMPOSABLE) { return CallSiteType.DECOMPOSABLE_EXPRESSION; } else { Preconditions.checkState(type == DecompositionType.UNDECOMPOSABLE); } } } return CallSiteType.UNSUPPORTED; } /** * Inline a function which fulfills the requirements of * canInlineReferenceAsStatementBlock into the call site, replacing the * parent expression. */ private Node inlineFunction( Node callNode, Node fnNode, String fnName) { Node parent = callNode.getParent(); Node grandParent = parent.getParent(); // TODO(johnlenz): Consider storing the callSite classification in the // reference object and passing it in here. CallSiteType callSiteType = classifyCallSite(callNode); Preconditions.checkArgument(callSiteType != CallSiteType.UNSUPPORTED); // Store the name for the result. This will be used to // replace "return expr" statements with "resultName = expr" // to replace String resultName = null; boolean needsDefaultReturnResult = true; switch (callSiteType) { case SIMPLE_ASSIGNMENT: resultName = parent.getFirstChild().getString(); break; case VAR_DECL_SIMPLE_ASSIGNMENT: resultName = parent.getString(); break; case SIMPLE_CALL: resultName = null; // "foo()" doesn't need a result. needsDefaultReturnResult = false; break; case EXPRESSION: resultName = getUniqueResultName(); needsDefaultReturnResult = false; // The intermediary result already // has the default value. break; case DECOMPOSABLE_EXPRESSION: throw new IllegalStateException( "Decomposable expressions must decomposed before inlining."); default: throw new IllegalStateException("Unexpected call site type."); } boolean isCallInLoop = NodeUtil.isWithinLoop(callNode); FunctionToBlockMutator mutator = new Function

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ToBlockMutator( compiler, this.safeNameIdSupplier); Node newBlock = mutator.mutate( fnName, fnNode, callNode, resultName, needsDefaultReturnResult, isCallInLoop); // TODO(nicksantos): Create a common mutation function that // can replace either a VAR name assignment, assignment expression or // a EXPR_RESULT. Node greatGrandParent = grandParent.getParent(); switch (callSiteType) { case VAR_DECL_SIMPLE_ASSIGNMENT: // Remove the call from the name node. parent.removeChild(parent.getFirstChild()); Preconditions.checkState(parent.getFirstChild() == null); // Add the call, after the VAR. greatGrandParent.addChildAfter(newBlock, grandParent); break; case SIMPLE_ASSIGNMENT: // The assignment is now part of the inline function so // replace it completely. Preconditions.checkState(NodeUtil.isExpressionNode(grandParent)); greatGrandParent.replaceChild(grandParent, newBlock); break; case SIMPLE_CALL: // If nothing is looking at the result just replace the call. Preconditions.checkState(NodeUtil.isExpressionNode(parent)); grandParent.replaceChild(parent, newBlock); break; case EXPRESSION: // TODO(johnlenz): Maybe change this so that movable and decomposable // expressions are handled the same way: The call is moved and // then handled by one the three basic cases, rather than // introducing a new case. Node injectionPoint = ExpressionDecomposer.findInjectionPoint(callNode); Preconditions.checkNotNull(injectionPoint); Node injectionPointParent = injectionPoint.getParent(); Preconditions.checkNotNull(injectionPointParent); Preconditions.checkState( NodeUtil.isStatementBlock(injectionPointParent)); // Declare the intermediate result name. newBlock.addChildrenToFront( NodeUtil.newVarNode(resultName, null) .copyInformationFromForTree(callNode)); // Inline the function before the selected injection point (before // the call). injectionPointParent.addChildBefore(newBlock, injectionPoint); // Replace the call site with a reference to the intermediate // result name. parent.replaceChild(callNode, Node.newString(Token.NAME, resultName)); break; default: throw new IllegalStateException("Unexpected call site type."); } return newBlock; } /** * Checks if the given function matches the criteria for an inlinable * function, and if so, adds it to our set of inlinable functions. */ boolean isDirectCallNodeReplacementPossible(Node fnNode) { // Only inline single-statement functions Node block = NodeUtil.getFunctionBody(fnNode); // Check if this function is suitable for direct replacement of a CALL node: // a function that consists of single return that returns an expression. if (!block.hasChildren()) { // special case empty functions. return true;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } else if (block.hasOneChild()) { // Only inline functions that return something. if (block.getFirstChild().getType() == Token.RETURN && block.getFirstChild().getFirstChild() != null) { return true; } } return false; } enum CanInlineResult { YES, AFTER_DECOMPOSITION, NO } /** * Determines whether a function can be inlined at a particular call site. * There are several criteria that the function and reference must hold in * order for the functions to be inlined: * - It must be a simple call, or assignment, or var initialization. * <pre> * f(); * a = foo(); * var a = foo(); * </pre> */ private CanInlineResult canInlineReferenceAsStatementBlock( NodeTraversal t, Node callNode, Node fnNode, Set<String> namesToAlias) { CallSiteType callSiteType = classifyCallSite(callNode); if (callSiteType == CallSiteType.UNSUPPORTED) { return CanInlineResult.NO; } if (!allowDecomposition && callSiteType == CallSiteType.DECOMPOSABLE_EXPRESSION) { return CanInlineResult.NO; } if (!callMeetsBlockInliningRequirements( t, callNode, fnNode, namesToAlias)) { return CanInlineResult.NO; } if (callSiteType == CallSiteType.DECOMPOSABLE_EXPRESSION) { return CanInlineResult.AFTER_DECOMPOSITION; } else { return CanInlineResult.YES; } } /** * Determines whether a function can be inlined at a particular call site. * - Don't inline if the calling function contains an inner function and * inlining would introduce new globals. */ private boolean callMeetsBlockInliningRequirements( NodeTraversal t, Node callNode, Node fnNode, Set<String> namesToAlias) { // Note: functions that contain function definitions are filtered out // in isCanidateFunction. // TODO(johnlenz): Determining if the called function contains VARs // or if the caller contains inner functions accounts for 20% of the // runtime cost of this pass. // Don't inline functions with var declarations into a scope with inner // functions as the new vars would leak into the inner function and // cause memory leaks. boolean fnContainsVars = NodeUtil.has( NodeUtil.getFunctionBody(fnNode), new NodeUtil.MatchDeclaration(), new NodeUtil.MatchShallowStatement()); boolean callerContainsFunction = false; if (!t.inGlobalScope()) { Node fnCaller = t.getScopeRoot(); Node fnCallerBody = fnCaller.getLastChild(); callerContainsFunction = NodeUtil.containsFunction(fnCallerBody); } if (fnContainsVars && callerContainsFunction) { return false; } // If the caller contains functions, verify we aren't adding any

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // additional VAR declarations because aliasing is needed. if (callerContainsFunction) { Map<String, Node> args = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); boolean hasArgs = !args.isEmpty(); if (hasArgs) { // Limit the inlining Set<String> allNamesToAlias = Sets.newHashSet(namesToAlias); FunctionArgumentInjector.maybeAddTempsForCallArguments( fnNode, args, allNamesToAlias, compiler.getCodingConvention()); if (!allNamesToAlias.isEmpty()) { return false; } } } return true; } /** * Determines whether a function can be inlined at a particular call site. * There are several criteria that the function and reference must hold in * order for the functions to be inlined: * 1) If a call's arguments have side effects, * the corresponding argument in the function must only be referenced once. * For instance, this will not be inlined: * <pre> * function foo(a) { return a + a } * x = foo(i++); * </pre> */ private CanInlineResult canInlineReferenceDirectly( Node callNode, Node fnNode) { if (!isDirectCallNodeReplacementPossible(fnNode)) { return CanInlineResult.NO; } Node block = fnNode.getLastChild(); // CALL NODE: [ NAME, ARG1, ARG2, ... ] Node cArg = callNode.getFirstChild().getNext(); // Functions called via 'call' and 'apply' have a this-object as // the first parameter, but this is not part of the called function's // parameter list. if (callNode.getFirstChild().getType() != Token.NAME) { if (NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Support replace this with a value. Preconditions.checkNotNull(cArg); Preconditions.checkState(cArg.getType() == Token.THIS); cArg = cArg.getNext(); } else { // ".apply" call should be filtered before this. Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode)); } } // FUNCTION NODE -> LP NODE: [ ARG1, ARG2, ... ] Node fnParam = NodeUtil.getFnParameters(fnNode).getFirstChild(); while (cArg != null || fnParam != null) { // For each named parameter check if a mutable argument use more than one. if (fnParam != null) { if (cArg != null) { // Check for arguments that are evaluated more than once. // Note: Unlike block inlining, there it is not possible that a // parameter reference will be in a loop. if (NodeUtil.mayEffectMutableState(cArg) && NodeUtil.getNameReferenceCount( block, fnParam.getString()) > 1) { return Can

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.ESTIMATED_IDENTIFIER_COST); Node block = fnNode.getLastChild(); if (!block.hasChildren()) { // Assume the inline cost is zero for empty functions. return -costDeltaFunctionOverhead; } if (mode == InliningMode.DIRECT) { // The part of the function that is inlined using direct inlining: // "return " (7) return -(costDeltaFunctionOverhead + 7); } else { int aliasCount = namesToAlias.size(); // Originally, we estimated purely base on the function code size, relying // on later optimizations. But that did not produce good results, so here // we try to estimate the something closer to the actual inlined coded. // NOTE 1: Result overhead is only if there is an assignment, but // getting that information would require some refactoring. // NOTE 2: The aliasing overhead is currently an under-estimate, // as some parameters are aliased because of the parameters used. // Perhaps we should just assume all parameters will be aliased? final int INLINE_BLOCK_OVERHEAD = 4; // "X:{}" final int PER_RETURN_OVERHEAD = 2; // "return" --> "break X" final int PER_RETURN_RESULT_OVERHEAD = 3; // "XX=" final int PER_ALIAS_OVERHEAD = 3; // "XX=" // TODO(johnlenz): Counting the number of returns is relatively expensive // this information should be determined during the traversal and // cached. int returnCount = NodeUtil.getNodeTypeReferenceCount( block, Token.RETURN, new NodeUtil.MatchShallowStatement()); int resultCount = (returnCount > 0) ? returnCount - 1 : 0; int baseOverhead = (returnCount > 0) ? INLINE_BLOCK_OVERHEAD : 0; int overhead = baseOverhead + returnCount * PER_RETURN_OVERHEAD + resultCount * PER_RETURN_RESULT_OVERHEAD + aliasCount * PER_ALIAS_OVERHEAD; return (overhead - costDeltaFunctionOverhead); } } /** * Store the names of known constants to be used when classifying call-sites * in expressions. */ public void setKnownConstants(Set<String> knownConstants) { // This is only expected to be set once. The same set should be used // when evaluating call-sites and inlining calls. Preconditions.checkState(this.knownConstants.isEmpty()); this.knownConstants = knownConstants; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Prepare the AST before we do any checks or optimizations on it. * * This pass must run. It should bring the AST into a consistent state, * and add annotations where necessary. It should not make any transformations * on the tree that would lose source information, since we need that source * information for checks. * * @author johnlenz@google.com (John Lenz) */ class PrepareAst implements CompilerPass { private final AbstractCompiler compiler; private final boolean checkOnly; PrepareAst(AbstractCompiler compiler) { this(compiler, false); } PrepareAst(AbstractCompiler compiler, boolean checkOnly) { this.compiler = compiler; this.checkOnly = checkOnly; } private void reportChange() { if (checkOnly) { Preconditions.checkState(false, "normalizeNodeType constraints violated"); } } @Override public void process(Node externs, Node root) { if (checkOnly) { normalizeNodeTypes(root); } else { // Don't perform "PrepareAnnotations" when doing checks as // they currently aren't valid during sanity checks. In particular, // they DIRECT_EVAL shouldn't be applied after inlining has been // performed. if (externs != null) { NodeTraversal.traverse( compiler, externs, new PrepareAnnotations(compiler)); } if (root != null) { NodeTraversal.traverse( compiler, root, new PrepareAnnotations(compiler)); } } } /** * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code. */ private void normalizeNodeTypes(Node n) { if (n.getType() == Token.EXPR_VOID) { n.setType(Token.EXPR_RESULT); reportChange(); } // Remove unused properties to minimize differences between ASTs // produced by the two parsers. if (n.getType() == Token.FUNCTION) { Preconditions.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>checkState(n.getProp(Node.FUNCTION_PROP) == null); } normalizeBlocks(n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // This pass is run during the CompilerTestCase validation, so this // parent pointer check serves as a more general check. Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && n.getType() != Token.LABEL && n.getType() != Token.SWITCH) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n,c) && c.getType() != Token.BLOCK) { Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno()); newBlock.copyInformationFrom(n); n.replaceChild(c, newBlock); if (c.getType() != Token.EMPTY) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations implements NodeTraversal.Callback { private final CodingConvention convention; PrepareAnnotations(AbstractCompiler compiler) { this.convention = compiler.getCodingConvention(); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.OBJECTLIT) { normalizeObjectLiteralAnnotations(n); } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: annotateCalls(n); break; case Token.FUNCTION: annotateFunctions(n, parent); annotateDispatchers(n, parent); break; } } private void normalizeObjectLiteralAnnotations(Node objlit) { Preconditions.checkState(objlit.getType() == Token.OBJECTLIT); for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); normalizeObjectLiteralKeyAnnotations(objlit, key, value); } } /** * There are two types of calls we are interested in calls without explicit * "this" values (what we are call "free" calls) and direct call to eval. */ private void annotateCalls(Node n) { Preconditions.checkState(n.getType() == Token.CALL); // Keep track of of the "this" context of a

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.getType() == Token.NAME && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } } /** * Translate dispatcher info into the property expected node. */ private void annotateDispatchers(Node n, Node parent) { Preconditions.checkState(n.getType() == Token.FUNCTION); if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isJavaDispatch()) { if (parent.getType() == Token.ASSIGN) { Preconditions.checkState(parent.getLastChild() == n); n.putBooleanProp(Node.IS_DISPATCHER, true); } } } /** * In the AST that Rhino gives us, it needs to make a distinction * between jsdoc on the object literal node and jsdoc on the object literal * value. For example, * <pre> * var x = { * / JSDOC / * a: 'b', * c: / JSDOC / 'd' * }; * </pre> * * But in few narrow cases (in particular, function literals), it's * a lot easier for us if the doc is attached to the value. */ private void normalizeObjectLiteralKeyAnnotations( Node objlit, Node key, Node value) { Preconditions.checkState(objlit.getType() == Token.OBJECTLIT); if (key.getJSDocInfo() != null && value.getType() == Token.FUNCTION) { value.setJSDocInfo(key.getJSDocInfo()); } } /** * Annotate optional and var_arg function parameters. */ private void annotateFunctions(Node n, Node parent) { JSDocInfo fnInfo = NodeUtil.getFunctionInfo(n); // Compute which function parameters are optional and // which are var_args. Node args = n.getFirstChild().getNext(); for (Node arg = args.getFirstChild(); arg != null; arg = arg.getNext()) { String argName = arg.getString(); JSTypeExpression typeExpr = fnInfo == null ? null : fnInfo.getParameterType(argName); if (convention.isOptionalParameter(arg) || typeExpr != null && typeExpr.isOptionalArg()) { arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true); } if (convention.isVarArgsParameter(arg) || typeExpr != null && typeExpr.isVarArgs()) { arg.put

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> of superclass type", superObject, declaredSuper); } // Correct the super type. if (!subCtor.hasCachedValues()) { subCtor.setPrototypeBasedOn(superObject); } } } /** * Expect that the first type can be cast to the second type. The first type * should be either a subtype or supertype of the second. * * @param t The node traversal. * @param n The node where warnings should point. * @param type The type being cast from. * @param castType The type being cast to. */ void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) { castType = castType.restrictByNotNullOrUndefined(); type = type.restrictByNotNullOrUndefined(); if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) { if (shouldReport) { compiler.report( t.makeError(n, INVALID_CAST, castType.toString(), type.toString())); } registerMismatch(type, castType); } } /** * Expect that the given variable has not been declared with a type. * * @param sourceName The name of the source file we're in. * @param n The node where warnings should point to. * @param parent The parent of {@code n}. * @param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here * for the benefit of the warning. */ void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var, String variableName, JSType newType) { boolean allowDupe = false; if (n.getType() == Token.GETPROP) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. if (var.input == null) { n.setJSType(varType); if (parent.getType() == Token.VAR) { if (n.getFirstChild() !=

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.getType() == Token.FUNCTION); parent.setJSType(varType); } } else { // Always warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().getType() == Token.EXPR_RESULT) || !newType.equals(varType)) { if (shouldReport) { compiler.report( JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.getLineno()), varType.toString())); } } } } } /** * Expect that all properties on interfaces that this type implements are * implemented. */ void expectAllInterfacePropertiesImplemented(FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { if (implemented.getImplicitPrototype() != null) { for (String prop : implemented.getImplicitPrototype().getOwnPropertyNames()) { if (!instance.hasProperty(prop)) { Node source = type.getSource(); Preconditions.checkNotNull(source); String sourceName = (String) source.getProp(Node.SOURCENAME_PROP); sourceName = sourceName == null ? "" : sourceName; if (shouldReport) { compiler.report(JSError.make(sourceName, source, INTERFACE_METHOD_NOT_IMPLEMENTED, prop, implemented.toString(), instance.toString())); } registerMismatch(instance, implemented); } } } } } /** * Report a type mismatch */ private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSType required) { mismatch(t.getSourceName(), n, msg, found, required); } private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSTypeNative required) { mismatch(t, n, msg, found, getNativeType(required)); } private void mismatch(String sourceName, Node n, String msg, JSType found, JSType required) { registerMismatch(found, required); if (shouldReport) { compiler.report( JSError.make(sourceName, n, TYPE_MISMATCH_WARNING, formatFoundRequired(msg, found, required))); } } private void registerMismatch(JSType found, JSType required) { // Don't register a mismatch for differences in null or undefined or if the // code didn't downcast. found = found.restrictByNotNullOrUndefined(); required = required.restrictByNotNullOrUndefined(); if (found.canAssignTo(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>required) || required.canAssignTo(found)) { return; } mismatches.add(new TypeMismatch(found, required)); if (found instanceof FunctionType && required instanceof FunctionType) { FunctionType fnTypeA = ((FunctionType) found); FunctionType fnTypeB = ((FunctionType) required); Iterator<Node> paramItA = fnTypeA.getParameters().iterator(); Iterator<Node> paramItB = fnTypeB.getParameters().iterator(); while (paramItA.hasNext() && paramItB.hasNext()) { registerIfMismatch(paramItA.next().getJSType(), paramItB.next().getJSType()); } registerIfMismatch(fnTypeA.getReturnType(), fnTypeB.getReturnType()); } } private void registerIfMismatch(JSType found, JSType required) { if (found != null && required != null && !found.canAssignTo(required)) { registerMismatch(found, required); } } /** * Formats a found/required error message. */ private String formatFoundRequired(String description, JSType found, JSType required) { return MessageFormat.format(FOUND_REQUIRED, description, found, required); } /** * Given a node, get a human-readable name for the type of that node so * that will be easy for the programmer to find the original declaration. * * For example, if SubFoo's property "bar" might have the human-readable * name "Foo.prototype.bar". * * @param n The node. * @param dereference If true, the type of the node will be dereferenced * to an Object type, if possible. */ String getReadableJSTypeName(Node n, boolean dereference) { // If we're analyzing a GETPROP, the property may be inherited by the // prototype chain. So climb the prototype chain and find out where // the property was originally defined. if (n.getType() == Token.GETPROP) { ObjectType objectType = getJSType(n.getFirstChild()).dereference(); if (objectType != null) { String propName = n.getLastChild().getString(); while (objectType != null && !objectType.hasOwnProperty(propName)) { objectType = objectType.getImplicitPrototype(); } // Don't show complex function names or anonymous types. // Instead, try to get a human-readable type name. if (objectType != null && (objectType.getConstructor() != null || objectType.isFunctionPrototypeType())) { return objectType.toString() + "." + propName; } } } JSType type = getJSType(n); if (dereference) { ObjectType dereferenced = type.dereference(); if (dereferenced != null) { type = dereferenced; } } String qualifiedName = n.getQualifiedName(); if (type.isFunctionPrototypeType() || (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>FlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false); cfa.process(null, scope.getRootNode()); cfgStack.push(curCfg); curCfg = cfa.getCfg(); new GraphReachability<Node, ControlFlowGraph.Branch>(curCfg) .compute(curCfg.getEntry().getValue()); } @Override public void exitScope(NodeTraversal t) { curCfg = cfgStack.pop(); } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (parent == null) { return; } if (n.getType() == Token.FUNCTION || n.getType() == Token.SCRIPT) { return; } // Removes TRYs that had its CATCH removed and/or empty FINALLY. // TODO(dcc): Move the parts of this that don't require a control flow // graph to PeepholeRemoveDeadCode if (n.getType() == Token.TRY) { Node body = n.getFirstChild(); Node catchOrFinallyBlock = body.getNext(); Node finallyBlock = catchOrFinallyBlock.getNext(); if (!catchOrFinallyBlock.hasChildren() && (finallyBlock == null || !finallyBlock.hasChildren())) { n.removeChild(body); parent.replaceChild(n, body); compiler.reportCodeChange(); n = body; } } DiGraphNode<Node, Branch> gNode = curCfg.getDirectedGraphNode(n); if (gNode == null) { // Not in CFG. return; } if (gNode.getAnnotation() != GraphReachability.REACHABLE || (removeNoOpStatements && !NodeUtil.mayHaveSideEffects(n))) { removeDeadExprStatementSafely(n); return; } tryRemoveUnconditionalBranching(n); } /** * Tries to remove n if an unconditional branch node (break, continue or * return) if the target of n is the same as the the follow of n. That is, if * we remove n, the control flow remains the same. Also if n targets to * another unconditional branch, this function will recursively try to remove * the target branch as well. The reason why we want to cascade this removal * is because we only run this pass once. If we have code such as * * break -> break -> break * * where all 3 break's are useless. The order of removal matters. When we * first look at the first break, we see that it branches to the 2nd break. * However, if we remove the last break, the 2nd break becomes useless and * finally the first break becomes useless as well. * * @return The target of this jump. If the target is also useless jump, * the target of that useless jump recursively

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>. */ @SuppressWarnings("fallthrough") private Node tryRemoveUnconditionalBranching(Node n) { /* * For each of the unconditional branching control flow node, check to see * if the ControlFlowAnalysis.computeFollowNode of that node is same as * the branching target. If it is, the branch node is safe to be removed. * * This is not as clever as MinimizeExitPoints because it doesn't do any * if-else conversion but it handles more complicated switch statements * much nicer. */ // If n is null the target is the end of the function, nothing to do. if (n == null) { return n; } DiGraphNode<Node, Branch> gNode = curCfg.getDirectedGraphNode(n); if (gNode == null) { return n; } // If the parent is null, this mean whatever node it was there is now // useless and it has been removed by other logics in this pass. That node // while no longer exists in the AST, is still in the CFG because we // never update the graph as nodes are removed. if (n.getParent() == null) { List<DiGraphEdge<Node,Branch>> outEdges = gNode.getOutEdges(); if (outEdges.size() == 1) { return tryRemoveUnconditionalBranching( outEdges.get(0).getDestination().getValue()); } } switch (n.getType()) { case Token.BLOCK: if (n.hasChildren()) { Node first = n.getFirstChild(); return tryRemoveUnconditionalBranching(first); } else { return tryRemoveUnconditionalBranching( ControlFlowAnalysis.computeFollowNode(n)); } case Token.RETURN: if (n.hasChildren()) { break; } case Token.BREAK: case Token.CONTINUE: // We are looking for a control flow changing statement that always // branches to the same node. If removing it the control flow still // branches to that same node. It is safe to remove it. List<DiGraphEdge<Node,Branch>> outEdges = gNode.getOutEdges(); if (outEdges.size() == 1 && // If there is a next node, there is no chance this jump is useless. (n.getNext() == null || n.getNext().getType() == Token.FUNCTION)) { Preconditions.checkState(outEdges.get(0).getValue() == Branch.UNCOND); Node fallThrough = tryRemoveUnconditionalBranching( ControlFlowAnalysis.computeFollowNode(n)); Node nextCfgNode = outEdges.get(0).getDestination().getValue(); if (nextCfgNode == fallThrough) { removeDeadExprStatementSafely(n); return fallThrough; } } } return n; } private void removeDeadExprStatementSafely(Node n) { if (n.getType() == Token.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>EMPTY || (n.getType() == Token.BLOCK && !n.hasChildren())) { // Not always trivial to remove, let FoldContants work its magic later. return; } // Removing an unreachable DO node is messy because it means we still have // to execute one iteration. If the DO's body has breaks in the middle, it // can get even more trickier and code size might actually increase. switch (n.getType()) { case Token.DO: case Token.TRY: case Token.CATCH: case Token.FINALLY: return; } NodeUtil.redeclareVarsInsideBranch(n); compiler.reportCodeChange(); if (logger.isLoggable(Level.FINE)) { logger.fine("Removing " + n.toString()); } NodeUtil.removeChild(n.getParent(), n); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.parsing; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.mozilla.rhino.ScriptRuntime; /** * This class implements the scanner for JsDoc strings. * * It is heavily based on Rhino's TokenStream. * */ class JsDocTokenStream { /* * For chars - because we need something out-of-range * to check. (And checking EOF by exception is annoying.) * Note distinction from EOF token type! */ private final static int EOF_CHAR = -1; JsDocTokenStream(String sourceString) { this(sourceString, 0); } JsDocTokenStream(String sourceString, int lineno) { this(sourceString, lineno, 0); } JsDocTokenStream(String sourceString, int lineno, int initCharno) { Preconditions.checkNotNull(sourceString); this.lineno = lineno; this.sourceString = sourceString; this.sourceEnd = sourceString.length(); this.sourceCursor = this.cursor = 0; this.initLineno = lineno; this.initCharno = initCharno; } /** * Tokenizes JSDoc comments. */ @SuppressWarnings("fallthrough") final JsDocToken getJsDocToken() { int c; stringBufferTop = 0; for (;;) { // eat white spaces for (;;) { charno = -1; c = getChar(); if (c == EOF_CHAR) { return JsDocToken.EOF; } else if (c == '\n') { return JsDocToken.EOL; } else if (!isJSSpace(c)) { break; } } switch (c) { // annotation, e.g. @type or @constructor case '@': do { c = getChar(); if (isAlpha(c)) { addToString(c); } else { ungetChar(c); this.string = getStringFromBuffer(); stringBufferTop = 0; return JsDocToken.ANNOTATION; } } while (true); case '*': if (matchChar('/')) { return JsDocToken.EOC;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> ungetChar(c); this.string = getStringFromBuffer(); stringBufferTop = 0; return this.string; default: addToString(c); break; } } } final int getLineno() { return lineno; } final int getCharno() { return lineno == initLineno? initCharno + charno : charno; } final String getString() { return string; } final boolean eof() { return hitEOF; } private String getStringFromBuffer() { tokenEnd = cursor; return new String(stringBuffer, 0, stringBufferTop); } private void addToString(int c) { int N = stringBufferTop; if (N == stringBuffer.length) { char[] tmp = new char[stringBuffer.length * 2]; System.arraycopy(stringBuffer, 0, tmp, 0, N); stringBuffer = tmp; } stringBuffer[N] = (char)c; stringBufferTop = N + 1; } private void ungetChar(int c) { // can not unread past across line boundary assert(!(ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n')); ungetBuffer[ungetCursor++] = c; cursor--; } private boolean matchChar(int test) { int c = getCharIgnoreLineEnd(); if (c == test) { tokenEnd = cursor; return true; } else { ungetCharIgnoreLineEnd(c); return false; } } private static boolean isAlpha(int c) { // Use 'Z' < 'a' if (c <= 'Z') { return 'A' <= c; } else { return 'a' <= c && c <= 'z'; } } private boolean isJSDocString(int c) { switch (c) { case '@': case '*': case ',': case '>': case ':': case '(': case ')': case '{': case '}': case '[': case ']': case '?': case '!': case '|': case '=': case EOF_CHAR: case '\n': return false; default: return !isJSSpace(c); } } /* As defined in ECMA. jsscan.c uses C isspace() (which allows * \v, I think.) note that code in getChar() implicitly accepts * '\r' == \u000D as well. */ static boolean isJSSpace(int c) { if (c <= 127) { return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB; } else { return c == 0xA0 || Character.getType((char)c) == Character.SPACE_SEPARATOR; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private static boolean isJSFormatChar(int c) { return c > 127 && Character.getType((char)c) == Character.FORMAT; } /** * Allows the JSDocParser to update the character offset * so that getCharno() returns a valid character position. */ void update() { charno = getOffset(); } private int peekChar() { int c = getChar(); ungetChar(c); return c; } protected int getChar() { if (ungetCursor != 0) { cursor++; --ungetCursor; if (charno == -1) { charno = getOffset(); } return ungetBuffer[ungetCursor]; } for(;;) { int c; if (sourceCursor == sourceEnd) { hitEOF = true; if (charno == -1) { charno = getOffset(); } return EOF_CHAR; } cursor++; c = sourceString.charAt(sourceCursor++); if (lineEndChar >= 0) { if (lineEndChar == '\r' && c == '\n') { lineEndChar = '\n'; continue; } lineEndChar = -1; lineStart = sourceCursor - 1; lineno++; } if (c <= 127) { if (c == '\n' || c == '\r') { lineEndChar = c; c = '\n'; } } else { if (isJSFormatChar(c)) { continue; } if (ScriptRuntime.isJSLineTerminator(c)) { lineEndChar = c; c = '\n'; } } if (charno == -1) { charno = getOffset(); } return c; } } private int getCharIgnoreLineEnd() { if (ungetCursor != 0) { cursor++; --ungetCursor; if (charno == -1) { charno = getOffset(); } return ungetBuffer[ungetCursor]; } for(;;) { int c; if (sourceCursor == sourceEnd) { hitEOF = true; if (charno == -1) { charno = getOffset(); } return EOF_CHAR; } cursor++; c = sourceString.charAt(sourceCursor++); if (c <= 127) { if (c == '\n' || c == '\r') { lineEndChar = c; c = '\n'; } } else { if (isJSFormatChar(c)) { continue; } if (ScriptRuntime.isJSLineTerminator(c)) { lineEndChar = c; c = '\n'; } } if (charno == -1) { charno = getOffset(); } return c; } } private void unget

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * An abstract class whose implementations run peephole optimizations: * optimizations that look at a small section of code and either remove * that code (if it is not needed) or replaces it with smaller code. * */ abstract class AbstractPeepholeOptimization { private NodeTraversal currentTraversal; /** * Given a node to optimize and a traversal, optimize the node. Subclasses * should override to provide their own peephole optimization. * * @param subtree The subtree that will be optimized. * @return The new version of the subtree (or null if the subtree or one of * its parents was removed from the AST). If the subtree has not changed, * this method must return {@code subtree}. */ abstract Node optimizeSubtree(Node subtree); /** * Helper method for reporting an error to the compiler when applying a * peephole optimization. * * @param diagnostic The error type * @param n The node for which the error should be reported */ protected void error(DiagnosticType diagnostic, Node n) { JSError error = currentTraversal.makeError(n, diagnostic, n.toString()); currentTraversal.getCompiler().report(error); } /** * Helper method for telling the compiler that something has changed. * Subclasses must call these if they have changed the AST. */ protected void reportCodeChange() { Preconditions.checkNotNull(currentTraversal); currentTraversal.getCompiler().reportCodeChange(); } /** * Are the nodes equal for the purpose of inlining? * If type aware optimizations are on, type equality is checked. */ protected boolean areNodesEqualForInlining(Node n1, Node n2) { /* Our implementation delegates to the compiler. We provide this * method because we don't want to expose Compiler to PeepholeOptimizations. */ Preconditions.checkNotNull(currentTraversal); return currentTraversal.getCompiler().areNodesEqualForInlining(n1, n2); } /** * Is the current AST normalized? (e.g. has

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> the Normalize pass been run * and has the Denormalize pass not yet been run?) */ protected boolean isASTNormalized() { Preconditions.checkNotNull(currentTraversal); Preconditions.checkNotNull(currentTraversal.getCompiler()); return currentTraversal.getCompiler().isNormalized(); } /** * Informs the optimization that a traversal will begin. */ void beginTraversal(NodeTraversal traversal) { currentTraversal = traversal; } /** * Informs the optimization that a traversal has completed. */ void endTraversal(NodeTraversal traversal) { currentTraversal = null; } // NodeUtil's mayEffectMutableState and mayHaveSideEffects need access to the // compiler object, route them through here to give them access. /** * @return Whether the node may create new mutable state, or change existing * state. */ boolean mayEffectMutableState(Node n) { return NodeUtil.mayEffectMutableState(n, currentTraversal.getCompiler()); } /** * @return Whether the node may have side effects when executed. */ boolean mayHaveSideEffects(Node n) { return NodeUtil.mayHaveSideEffects(n, currentTraversal.getCompiler()); } /** * Check if the specified node is null or is still in the AST. */ @VisibleForTesting static Node validateResult(Node n) { done: { if (n != null && n.getType() != Token.SCRIPT && (n.getType() != Token.BLOCK || !n.isSyntheticBlock())) { for (Node parent : n.getAncestors()) { if (parent.getType() == Token.SCRIPT) { break done; } } Preconditions.checkState(false); } } return n; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Function; import com.google.common.base.Preconditions; import java.util.List; /** * Defines a way join a list of LatticeElements. */ interface JoinOp<L extends LatticeElement> extends Function<List<L>, L> { /** * An implementation of {@code JoinOp} that makes it easy to join to * lattice elements at a time. */ static abstract class BinaryJoinOp<L extends LatticeElement> implements JoinOp<L> { @Override public final L apply(List<L> values) { Preconditions.checkArgument(!values.isEmpty()); int size = values.size(); if (size == 1) { return values.get(0); } else if (size == 2) { return apply(values.get(0), values.get(1)); } else { int mid = computeMidPoint(size); return apply( apply(values.subList(0, mid)), apply(values.subList(mid, size))); } } /** * Creates a new lattice that will be the join of two input lattices. * * @return The join of {@code latticeA} and {@code latticeB}. */ abstract L apply(L latticeA, L latticeB); /** * Finds the midpoint of a list. The function will favor two lists of * even length instead of two lists of the same odd length. The list * must be at least length two. * * @param size Size of the list. */ static int computeMidPoint(int size) { int midpoint = size >>> 1; if (size > 4) { /* Any list longer than 4 should prefer an even split point * over the true midpoint, so that [0,6] splits at 2, not 3. */ midpoint &= -2; // (0xfffffffe) clears low bit so midpoint is even } return midpoint; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.regex.Pattern; /** * A peephole optimization that minimizes code by simplifying conditional * expressions, replacing IFs with HOOKs, replacing object constructors * with literals, and simplifying returns. * */ public class PeepholeSubstituteAlternateSyntax extends AbstractPeepholeOptimization { private static final int AND_PRECEDENCE = NodeUtil.precedence(Token.AND); private static final int OR_PRECEDENCE = NodeUtil.precedence(Token.OR); static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS = DiagnosticType.error( "JSC_INVALID_REGULAR_EXPRESSION_FLAGS", "Invalid flags to RegExp constructor: {0}"); static final Predicate<Node> DONT_TRAVERSE_FUNCTIONS_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node input) { return input.getType() != Token.FUNCTION; } }; /** * Tries apply our various peephole minimizations on the passed in node. */ @Override @SuppressWarnings("fallthrough") public Node optimizeSubtree(Node node) { switch(node.getType()) { case Token.RETURN: return tryReduceReturn(node); case Token.NOT: tryMinimizeCondition(node.getFirstChild()); return tryMinimizeNot(node); case Token.IF: tryMinimizeCondition(node.getFirstChild()); return tryMinimizeIf(node); case Token.EXPR_RESULT: tryMinimizeCondition(node.getFirstChild()); return node; case Token.HOOK: tryMinimizeCondition(node.getFirstChild()); return node; case Token.WHILE: case Token.DO: tryMinimizeCondition(NodeUtil.getConditionExpression(node)); return node; case Token.FOR: if (!NodeUtil.isForIn(node

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>)) { tryMinimizeCondition(NodeUtil.getConditionExpression(node)); } return node; case Token.NEW: node = tryFoldStandardConstructors(node); if (node.getType() != Token.CALL) { return node; } // Fall through on purpose because tryFoldStandardConstructors() may // convert a NEW node into a CALL node case Token.CALL: return tryFoldLiteralConstructor(node); default: return node; //Nothing changed } } /** * Reduce "return undefined" or "return void 0" to simply "return". * * Returns the replacement for n, or the original if no change was made. */ private Node tryReduceReturn(Node n) { Node result = n.getFirstChild(); boolean possibleException = result != null && ControlFlowAnalysis.mayThrowException(result); // Try to use a substitute that with a break because it is shorter. // First lets pretend it is a break with no labels. Node breakTarget = n; boolean safe = true; for (;!ControlFlowAnalysis.isBreakTarget(breakTarget, null /* no label */); breakTarget = breakTarget.getParent()) { if (NodeUtil.isFunction(breakTarget) || breakTarget.getType() == Token.SCRIPT) { // We can switch the return to a break if the return value has // side effect and it must encounter a finally. // example: return alert('a') -> finally { alert('b') } -> // return alert('a') // prints a then b. If the first return is a break, // it prints b then a. safe = false; break; } } Node follow = ControlFlowAnalysis.computeFollowNode(breakTarget); // Skip pass all the finally blocks because both the break and return will // also trigger all the finally blocks. However, the order of execution is // slightly changed. Consider: // // return a() -> finally { b() } -> return a() // // which would call a() first. However, changing the first return to a // break will result in calling b(). while (follow != null && NodeUtil.isTryFinallyNode(follow.getParent(), follow)) { if (result != null && // TODO(user): Use the new side effects API for more accuracy. (NodeUtil.canBeSideEffected(result) || NodeUtil.mayHaveSideEffects(result))) { safe = false; break; } follow = ControlFlowAnalysis.computeFollowNode(follow); } if (safe) { if (follow == null) { // When follow is null, this mean the follow of a break target is the // end of a function. This means a break is same as return. if (result == null) { n.setType(Token.BREAK); reportCodeChange(); return n; } } else if (follow.getType() == Token.RETURN && (result == follow.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>getFirstChild() || (result != null && follow.hasChildren() && result.checkTreeEqualsSilent(follow.getFirstChild())) && ControlFlowAnalysis.getExceptionHandler(n) == ControlFlowAnalysis.getExceptionHandler(follow) )) { // When the follow is a return, if both doesn't return anything // or both returns the same thing. This mean we can replace it with a // break. n.removeChildren(); n.setType(Token.BREAK); reportCodeChange(); return n; } // If any of the above is executed, we must return because n is no longer // a "return" node. } // TODO(user): consider cases such as if (x) { return 1} return 1; if (result != null) { switch (result.getType()) { case Token.VOID: Node operand = result.getFirstChild(); if (!mayHaveSideEffects(operand)) { n.removeFirstChild(); reportCodeChange(); } break; case Token.NAME: String name = result.getString(); if (name.equals("undefined")) { n.removeFirstChild(); reportCodeChange(); } break; default: //Do nothing break; } } return n; } /** * Try to minimize NOT nodes such as !(x==y). * * Returns the replacement for n or the original if no change was made */ private Node tryMinimizeNot(Node n) { Node parent = n.getParent(); Node notChild = n.getFirstChild(); // negative operator of the current one : == -> != for instance. int complementOperator; switch (notChild.getType()) { case Token.EQ: complementOperator = Token.NE; break; case Token.NE: complementOperator = Token.EQ; break; case Token.SHEQ: complementOperator = Token.SHNE; break; case Token.SHNE: complementOperator = Token.SHEQ; break; // GT, GE, LT, LE are not handled in this because !(x<NaN) != x>=NaN. default: return n; } Node newOperator = n.removeFirstChild(); newOperator.setType(complementOperator); parent.replaceChild(n, newOperator); reportCodeChange(); return newOperator; } /** * Try turning IF nodes into smaller HOOKs * * Returns the replacement for n or the original if no replacement was * necessary. */ private Node tryMinimizeIf(Node n) { Node parent = n.getParent(); Node cond = n.getFirstChild(); /* If the condition is a literal, we'll let other * optimizations try to remove useless code. */ if (NodeUtil.isLiteralValue(cond, true)) { return n; } Node thenBranch = cond.getNext(); Node elseBranch = thenBranch.getNext(); if (elseBranch == null) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (isExpressBlock(thenBranch)) { Node expr = getBlockExpression(thenBranch); if (isPropertyAssignmentInExpression(expr)) { // Keep opportunities for CollapseProperties such as // a.longIdentifier || a.longIdentifier = ... -> var a = ...; return n; } if (cond.getType() == Token.NOT) { // if(!x)bar(); -> x||bar(); if (isLowerPrecedenceInExpression(cond, OR_PRECEDENCE) && isLowerPrecedenceInExpression(expr.getFirstChild(), OR_PRECEDENCE)) { // It's not okay to add two sets of parentheses. return n; } Node or = new Node(Token.OR, cond.removeFirstChild(), expr.removeFirstChild()).copyInformationFrom(n); Node newExpr = NodeUtil.newExpr(or); parent.replaceChild(n, newExpr); reportCodeChange(); return newExpr; } // if(x)foo(); -> x&&foo(); if (isLowerPrecedenceInExpression(cond, AND_PRECEDENCE) || isLowerPrecedenceInExpression(expr.getFirstChild(), AND_PRECEDENCE)) { // One additional set of parentheses isn't worth it. return n; } n.removeChild(cond); Node and = new Node(Token.AND, cond, expr.removeFirstChild()) .copyInformationFrom(n); Node newExpr = NodeUtil.newExpr(and); parent.replaceChild(n, newExpr); reportCodeChange(); return newExpr; } return n; } /* TODO(dcc) This modifies the siblings of n, which is undesirable for a * peephole optimization. This should probably get moved to another pass. */ tryRemoveRepeatedStatements(n); // if(!x)foo();else bar(); -> if(x)bar();else foo(); // An additional set of curly braces isn't worth it. if (cond.getType() == Token.NOT && !consumesDanglingElse(elseBranch)) { n.replaceChild(cond, cond.removeFirstChild()); n.removeChild(thenBranch); n.addChildToBack(thenBranch); reportCodeChange(); return n; } // if(x)return 1;else return 2; -> return x?1:2; if (isReturnExpressBlock(thenBranch) && isReturnExpressBlock(elseBranch)) { Node thenExpr = getBlockReturnExpression(thenBranch); Node elseExpr = getBlockReturnExpression(elseBranch); n.removeChild(cond); thenExpr.detachFromParent(); elseExpr.detachFromParent(); // note - we ignore any cases with "return;", technically this // can be converted to "return undefined;" or some variant, but // that does not help code size. Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> .copyInformationFrom(n); Node returnNode = new Node(Token.RETURN, hookNode); parent.replaceChild(n, returnNode); reportCodeChange(); return returnNode; } boolean thenBranchIsExpressionBlock = isExpressBlock(thenBranch); boolean elseBranchIsExpressionBlock = isExpressBlock(elseBranch); if (thenBranchIsExpressionBlock && elseBranchIsExpressionBlock) { Node thenOp = getBlockExpression(thenBranch).getFirstChild(); Node elseOp = getBlockExpression(elseBranch).getFirstChild(); if (thenOp.getType() == elseOp.getType()) { // if(x)a=1;else a=2; -> a=x?1:2; if (NodeUtil.isAssignmentOp(thenOp)) { Node lhs = thenOp.getFirstChild(); if (areNodesEqualForInlining(lhs, elseOp.getFirstChild()) && // if LHS has side effects, don't proceed [since the optimization // evaluates LHS before cond] // NOTE - there are some circumstances where we can // proceed even if there are side effects... !mayEffectMutableState(lhs)) { n.removeChild(cond); Node assignName = thenOp.removeFirstChild(); Node thenExpr = thenOp.removeFirstChild(); Node elseExpr = elseOp.getLastChild(); elseOp.removeChild(elseExpr); Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr) .copyInformationFrom(n); Node assign = new Node(thenOp.getType(), assignName, hookNode) .copyInformationFrom(thenOp); Node expr = NodeUtil.newExpr(assign); parent.replaceChild(n, expr); reportCodeChange(); return expr; } } else if (NodeUtil.isCall(thenOp)) { // if(x)foo();else bar(); -> x?foo():bar() n.removeChild(cond); thenOp.detachFromParent(); elseOp.detachFromParent(); Node hookNode = new Node(Token.HOOK, cond, thenOp, elseOp) .copyInformationFrom(n); Node expr = NodeUtil.newExpr(hookNode); parent.replaceChild(n, expr); reportCodeChange(); return expr; } } return n; } boolean thenBranchIsVar = isVarBlock(thenBranch); boolean elseBranchIsVar = isVarBlock(elseBranch); // if(x)var y=1;else y=2 -> var y=x?1:2 if (thenBranchIsVar && elseBranchIsExpressionBlock && NodeUtil.isAssign(getBlockExpression(elseBranch).getFirstChild())) { Node var = getBlockVar(thenBranch); Node elseAssign = getBlockExpression(elseBranch).getFirstChild(); Node name1 = var.getFirstChild(); Node maybeName2 = elseAssign.getFirstChild(); if (name1.hasChildren() && maybeName

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>2.getType() == Token.NAME && name1.getString().equals(maybeName2.getString())) { Node thenExpr = name1.removeChildren(); Node elseExpr = elseAssign.getLastChild().detachFromParent(); cond.detachFromParent(); Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr) .copyInformationFrom(n); var.detachFromParent(); name1.addChildrenToBack(hookNode); parent.replaceChild(n, var); reportCodeChange(); return var; } // if(x)y=1;else var y=2 -> var y=x?1:2 } else if (elseBranchIsVar && thenBranchIsExpressionBlock && NodeUtil.isAssign(getBlockExpression(thenBranch).getFirstChild())) { Node var = getBlockVar(elseBranch); Node thenAssign = getBlockExpression(thenBranch).getFirstChild(); Node maybeName1 = thenAssign.getFirstChild(); Node name2 = var.getFirstChild(); if (name2.hasChildren() && maybeName1.getType() == Token.NAME && maybeName1.getString().equals(name2.getString())) { Node thenExpr = thenAssign.getLastChild().detachFromParent(); Node elseExpr = name2.removeChildren(); cond.detachFromParent(); Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr) .copyInformationFrom(n); var.detachFromParent(); name2.addChildrenToBack(hookNode); parent.replaceChild(n, var); reportCodeChange(); return var; } } return n; } /** * Try to remove duplicate statements from IF blocks. For example: * * if (a) { * x = 1; * return true; * } else { * x = 2; * return true; * } * * becomes: * * if (a) { * x = 1; * } else { * x = 2; * } * return true; * * @param n The IF node to examine. */ private void tryRemoveRepeatedStatements(Node n) { Preconditions.checkState(n.getType() == Token.IF); Node parent = n.getParent(); if (!NodeUtil.isStatementBlock(parent)) { // If the immediate parent is something like a label, we // can't move the statement, so bail. return; } Node cond = n.getFirstChild(); Node trueBranch = cond.getNext(); Node falseBranch = trueBranch.getNext(); Preconditions.checkNotNull(trueBranch); Preconditions.checkNotNull(falseBranch); while (true) { Node lastTrue = trueBranch.getLastChild(); Node lastFalse = falseBranch.getLastChild(); if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(last

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>True, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { return NodeUtil.isExpressionNode(n.getFirstChild()); } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isExpressBlock(n)); return n.getFirstChild(); } /** * @return Whether the node is a block with a single statement that is * an return. */ private boolean isReturnExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node first = n.getFirstChild(); if (first.getType() == Token.RETURN) { return first.hasOneChild(); } } } return false; } /** * @return The expression that is part of the return. */ private Node getBlockReturnExpression(Node n) { Preconditions.checkState(isReturnExpressBlock(n)); return n.getFirstChild().getFirstChild(); } /** * @return Whether the node is a block with a single statement that is * a VAR declaration of a single variable. */ private boolean isVarBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node first = n.getFirstChild(); if (first.getType() == Token.VAR) { return first.hasOneChild(); } } } return false; } /** * @return The var node. */ private Node getBlockVar(Node n) { Preconditions.checkState(isVarBlock(n)); return n.getFirstChild(); } /** * Does a statement consume a 'dangling else'? A statement consumes * a 'dangling else' if an 'else' token following the statement * would be considered by the parser to be part of the statement. */ private boolean consumesDanglingElse(Node n) { while (true) { switch (n.getType()) { case Token.IF: if (n.getChildCount() < 3) { return true; } // This IF node has no else clause. n = n.getLastChild(); continue; case Token.WITH: case Token.WHILE: case Token.FOR: n = n.getLastChild(); continue; default: return false; } } } /** * Does the expression contain an operator with lower precedence than * the argument? */

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private boolean isLowerPrecedenceInExpression(Node n, final int precedence) { Predicate<Node> isLowerPrecedencePredicate = new Predicate<Node>() { @Override public boolean apply(Node input) { return NodeUtil.precedence(input.getType()) < precedence; } }; return NodeUtil.has(n, isLowerPrecedencePredicate, DONT_TRAVERSE_FUNCTIONS_PREDICATE); } /** * Does the expression contain a property assignment? */ private boolean isPropertyAssignmentInExpression(Node n) { Predicate<Node> isPropertyAssignmentInExpressionPredicate = new Predicate<Node>() { @Override public boolean apply(Node input) { return (input.getType() == Token.GETPROP && input.getParent().getType() == Token.ASSIGN); } }; return NodeUtil.has(n, isPropertyAssignmentInExpressionPredicate, DONT_TRAVERSE_FUNCTIONS_PREDICATE); } /** * Try to minimize conditions expressions, as there are additional * assumptions that can be made when it is known that the final result * is a boolean. * * The following transformations are done recursively: * !(x||y) --> !x&&!y * !(x&&y) --> !x||!y * !!x --> x * Thus: * !(x&&!y) --> !x||!!y --> !x||y * * Returns the replacement for n, or the original if no change was made */ private Node tryMinimizeCondition(Node n) { Node parent = n.getParent(); switch (n.getType()) { case Token.NOT: Node first = n.getFirstChild(); switch (first.getType()) { case Token.NOT: { Node newRoot = first.removeFirstChild(); parent.replaceChild(n, newRoot); reportCodeChange(); // No need to traverse, tryMinimizeCondition is called on the // NOT children are handled below. return newRoot; } case Token.AND: case Token.OR: { Node leftParent = first.getFirstChild(); Node rightParent = first.getLastChild(); if (leftParent.getType() == Token.NOT && rightParent.getType() == Token.NOT) { Node left = leftParent.removeFirstChild(); Node right = rightParent.removeFirstChild(); int newOp = (first.getType() == Token.AND) ? Token.OR : Token.AND; Node newRoot = new Node(newOp, left, right); parent.replaceChild(n, newRoot); reportCodeChange(); // No need to traverse, tryMinimizeCondition is called on the // AND and OR children below. return newRoot; } } break; } // No need to traverse, tryMinimizeCondition is called on the NOT // children in the general case in the main post-order traversal. return n; case Token.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>OR: case Token.AND: { Node left = n.getFirstChild(); Node right = n.getLastChild(); // Because the expression is in a boolean context minimize // the children, this can't be done in the general case. left = tryMinimizeCondition(left); right = tryMinimizeCondition(right); // Remove useless conditionals // Handle four cases: // x || false --> x // x || true --> true // x && true --> x // x && false --> false TernaryValue rightVal = NodeUtil.getBooleanValue(right); if (NodeUtil.getBooleanValue(right) != TernaryValue.UNKNOWN) { int type = n.getType(); Node replacement = null; boolean rval = rightVal.toBoolean(true); // (x || FALSE) => x // (x && TRUE) => x if (type == Token.OR && !rval || type == Token.AND && rval) { replacement = left; } else if (!mayHaveSideEffects(left)) { replacement = right; } if (replacement != null) { n.detachChildren(); parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } } return n; } case Token.HOOK: { Node condition = n.getFirstChild(); Node trueNode = n.getFirstChild().getNext(); Node falseNode = n.getLastChild(); // Because the expression is in a boolean context minimize // the result children, this can't be done in the general case. // The condition is handled in the general case in #optimizeSubtree trueNode = tryMinimizeCondition(trueNode); falseNode = tryMinimizeCondition(falseNode); // Handle four cases: // x ? true : false --> x // x ? false : true --> !x // x ? true : y --> x || y // x ? y : false --> x && y Node replacement = null; if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE && NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) { // Remove useless conditionals, keep the condition condition.detachFromParent(); replacement = condition; } else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.FALSE && NodeUtil.getBooleanValue(falseNode) == TernaryValue.TRUE) { // Remove useless conditionals, keep the condition condition.detachFromParent(); replacement = new Node(Token.NOT, condition); } else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE) { // Remove useless true case. n.detachChildren(); replacement = new Node(Token.OR, condition, falseNode); } else if (NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) { // Remove useless false case n.detachChildren(); replacement = new Node(Token

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.AND, condition, trueNode); } if (replacement != null) { parent.replaceChild(n, replacement); n = replacement; reportCodeChange(); } return n; } default: // while(true) --> while(1) TernaryValue nVal = NodeUtil.getBooleanValue(n); if (nVal != TernaryValue.UNKNOWN) { boolean result = nVal.toBoolean(true); int equivalentResult = result ? 1 : 0; return maybeReplaceChildWithNumber(n, parent, equivalentResult); } // We can't do anything else currently. return n; } } /** * Replaces a node with a number node if the new number node is not equivalent * to the current node. * * Returns the replacement for n if it was replaced, otherwise returns n. */ private Node maybeReplaceChildWithNumber(Node n, Node parent, int num) { Node newNode = Node.newNumber(num); if (!newNode.isEquivalentTo(n)) { parent.replaceChild(n, newNode); reportCodeChange(); return newNode; } return n; } private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS = // String, Number, and Boolean functions return non-object types, whereas // new String, new Number, and new Boolean return object types, so don't // include them here. ImmutableSet.of( "Object", "Array", "RegExp", "Error" ); /** * Fold "new Object()" to "Object()". */ private Node tryFoldStandardConstructors(Node n) { Preconditions.checkState(n.getType() == Token.NEW); // If name normalization has been run then we know that // new Object() does in fact refer to what we think it is // and not some custom-defined Object(). if (isASTNormalized()) { if (n.getFirstChild().getType() == Token.NAME) { String className = n.getFirstChild().getString(); if (STANDARD_OBJECT_CONSTRUCTORS.contains(className)) { n.setType(Token.CALL); reportCodeChange(); } } } return n; } /** * Replaces a new Array or Object node with an object literal, unless the * call to Array or Object is to a local function with the same name. */ private Node tryFoldLiteralConstructor(Node n) { Preconditions.checkArgument(n.getType() == Token.CALL || n.getType() == Token.NEW); Node constructorNameNode = n.getFirstChild(); Node newLiteralNode = null; // We require the AST to be normalized to ensure that, say, // Object() really refers to the built-in Object constructor // and not a user-defined constructor with the same name. if (isASTNormalized() && Token.NAME == constructorNameNode.getType()) { String className = constructorNameNode.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>getString(); if ("RegExp".equals(className)) { // "RegExp("boo", "g")" --> /boo/g return tryFoldRegularExpressionConstructor(n); } else { boolean constructorHasArgs = constructorNameNode.getNext() != null; if ("Object".equals(className) && !constructorHasArgs) { // "Object()" --> "{}" newLiteralNode = new Node(Token.OBJECTLIT); } else if ("Array".equals(className)) { // "Array(arg0, arg1, ...)" --> "[arg0, arg1, ...]" Node arg0 = constructorNameNode.getNext(); FoldArrayAction action = isSafeToFoldArrayConstructor(arg0); if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS || action == FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS) { newLiteralNode = new Node(Token.ARRAYLIT); n.removeChildren(); if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS) { newLiteralNode.addChildrenToFront(arg0); } } } if (newLiteralNode != null) { n.getParent().replaceChild(n, newLiteralNode); reportCodeChange(); return newLiteralNode; } } } return n; } private static enum FoldArrayAction { NOT_SAFE_TO_FOLD, SAFE_TO_FOLD_WITH_ARGS, SAFE_TO_FOLD_WITHOUT_ARGS} /** * Checks if it is safe to fold Array() constructor into []. It can be * obviously done, if the initial constructor has either no arguments or * at least two. The remaining case may be unsafe since Array(number) * actually reserves memory for an empty array which contains number elements. */ private FoldArrayAction isSafeToFoldArrayConstructor(Node arg) { FoldArrayAction action = FoldArrayAction.NOT_SAFE_TO_FOLD; if (arg == null) { action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS; } else if (arg.getNext() != null) { action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; } else { switch (arg.getType()) { case (Token.STRING): // "Array('a')" --> "['a']" action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; break; case (Token.NUMBER): // "Array(0)" --> "[]" if (arg.getDouble() == 0) { action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS; } break; case (Token.ARRAYLIT): // "Array([args])" --> "[[args]]" action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; break; default: } } return action; } private Node tryFoldRegularExpressionConstructor(Node n) { Node parent =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> n.getParent(); Node constructor = n.getFirstChild(); Node pattern = constructor.getNext(); // e.g. ^foobar$ Node flags = null != pattern ? pattern.getNext() : null; // e.g. gi // Only run on normalized AST to make sure RegExp() is actually // the RegExp we expect (if the AST has been normalized then // other RegExp's will have been renamed to something like RegExp$1) if (!isASTNormalized()) { return n; } if (null == pattern || (null != flags && null != flags.getNext())) { // too few or too many arguments return n; } if (// is pattern folded pattern.getType() == Token.STRING // make sure empty pattern doesn't fold to // && !"".equals(pattern.getString()) // NOTE(nicksantos): Make sure that the regexp isn't longer than // 100 chars, or it blows up the regexp parser in Opera 9.2. && pattern.getString().length() < 100 && (null == flags || flags.getType() == Token.STRING) // don't escape patterns with unicode escapes since Safari behaves badly // (read can't parse or crashes) on regex literals with unicode escapes && !containsUnicodeEscape(pattern.getString())) { // Make sure that / is escaped, so that it will fit safely in /brackets/. // pattern is a string value with \\ and similar already escaped pattern = makeForwardSlashBracketSafe(pattern); Node regexLiteral; if (null == flags || "".equals(flags.getString())) { // fold to /foobar/ regexLiteral = new Node(Token.REGEXP, pattern); } else { // fold to /foobar/gi if (!areValidRegexpFlags(flags.getString())) { error(INVALID_REGULAR_EXPRESSION_FLAGS, flags); return n; } if (!areSafeFlagsToFold(flags.getString())) { return n; } n.removeChild(flags); regexLiteral = new Node(Token.REGEXP, pattern, flags); } parent.replaceChild(n, regexLiteral); reportCodeChange(); return regexLiteral; } return n; } private static final Pattern REGEXP_FLAGS_RE = Pattern.compile("^[gmi]*$"); /** * are the given flags valid regular expression flags? * Javascript recognizes several suffix flags for regular expressions, * 'g' - global replace, 'i' - case insensitive, 'm' - multi-line. * They are case insensitive, and javascript does not recognize the extended * syntax mode, single-line mode, or expression replacement mode from perl5. */ private static boolean areValidRegexpFlags(String flags) { return REGEXP_FLAGS_RE.matcher(flags).matches(); } /** * are the given flags safe to fold? * We don't fold the regular expression if global ('g') flag is on,

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.graph; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.javascript.jscomp.graph.FixedPointGraphTraversal.EdgeCallback; /** * Computes all the reachable nodes. Upon execution of {@link #compute(Object)}, * the graph nodes will be annotated with {@link #REACHABLE} if it is reachable * from the specified entry node. * * @see GraphNode#getAnnotation() */ public class GraphReachability<N, E> implements EdgeCallback<N, E> { // TODO(user): This should work for undirected graphs when // FixedPointGraphTraversal accepts them. private final DiGraph<N, E> graph; private final Predicate<EdgeTuple<N, E>> edgePredicate; public GraphReachability(DiGraph<N, E> graph) { this(graph, null); } /** * @param graph The graph. * @param edgePredicate Given the predecessor P of the a node S and the edge * coming from P to S, this predicate should return true if S is * reachable from P using the edge. */ public GraphReachability(DiGraph<N, E> graph, Predicate<EdgeTuple<N, E>> edgePredicate) { this.graph = graph; this.edgePredicate = edgePredicate; } public void compute(N entry) { graph.clearNodeAnnotations(); graph.getNode(entry).setAnnotation(REACHABLE); FixedPointGraphTraversal.newTraversal(this) .computeFixedPoint(graph, entry); } public void recompute(N reachableNode) { GraphNode<N, E> newReachable = graph.getNode(reachableNode); Preconditions.checkState(newReachable.getAnnotation() != REACHABLE); newReachable.setAnnotation(REACHABLE); FixedPointGraphTraversal.newTraversal(this) .computeFixedPoint(graph, reachableNode); } @Override public boolean traverseEdge(N source, E e, N destination) { if (graph.getNode(source).getAnnotation() == REACHABLE && (edgePredicate == null || edgePredicate.apply(new EdgeTuple<N, E>(source, e, destination)))) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> DiagnosticType.error( "JSC_INVALID_DEFINE_INIT_ERROR", "illegal initialization of @define variable {0}"); static final DiagnosticType NON_GLOBAL_DEFINE_INIT_ERROR = DiagnosticType.error( "JSC_NON_GLOBAL_DEFINE_INIT_ERROR", "@define variable {0} assignment must be global"); static final DiagnosticType DEFINE_NOT_ASSIGNABLE_ERROR = DiagnosticType.error( "@define variable cannot be assigned here", "@define variable {0} cannot be assigned due to unsafe code at {1}."); private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE = new MessageFormat("line {0} of {1}"); /** * Create a pass that overrides define constants. * * TODO(nicksantos): Write a builder to help JSCompiler induce * {@code replacements} from command-line flags * * @param replacements A hash table of names of defines to their replacements. * All replacements <b>must</b> be literals. */ ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) { this.compiler = compiler; dominantReplacements = replacements; } /** * Injects a pre-computed global namespace, so that the same namespace * can be re-used for multiple check passes. Returns {@code this} for * easy chaining. */ ProcessDefines injectNamespace(GlobalNamespace namespace) { this.namespace = namespace; return this; } public void process(Node externs, Node root) { if (namespace == null) { namespace = new GlobalNamespace(compiler, root); } overrideDefines(collectDefines(root, namespace)); } private void overrideDefines(Map<String, DefineInfo> allDefines) { boolean changed = false; for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) { String defineName = def.getKey(); DefineInfo info = def.getValue(); Node inputValue = dominantReplacements.get(defineName); Node finalValue = inputValue != null ? inputValue : info.getLastValue(); if (finalValue != info.initialValue) { info.initialValueParent.replaceChild( info.initialValue, finalValue.cloneTree()); compiler.addToDebugLog("Overriding @define variable " + defineName); changed = changed || finalValue.getType() != info.initialValue.getType() || !finalValue.isEquivalentTo(info.initialValue); } } if (changed) { compiler.reportCodeChange(); } Set<String> unusedReplacements = dominantReplacements.keySet(); unusedReplacements.removeAll(allDefines.keySet()); unusedReplacements.removeAll(KNOWN_DEFINES); for (String unknownDefine : unusedReplacements) { compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine)); } } private static String format(MessageFormat

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> format, Object... params) { return format.format(params); } /** * Only defines of literal number, string, or boolean are supported. */ private boolean isValidDefineType(JSTypeExpression expression) { JSType type = expression.evaluate(null, compiler.getTypeRegistry()); return !type.isUnknownType() && type.isSubtype( compiler.getTypeRegistry().getNativeType( JSTypeNative.NUMBER_STRING_BOOLEAN)); } /** * Finds all defines, and creates a {@link DefineInfo} data structure for * each one. * @return A map of {@link DefineInfo} structures, keyed by name. */ private Map<String, DefineInfo> collectDefines(Node root, GlobalNamespace namespace) { // Find all the global names with a @define annotation List<Name> allDefines = Lists.newArrayList(); for (Name name : namespace.getNameIndex().values()) { if (name.docInfo != null && name.docInfo.isDefine()) { // Process defines should not depend on check types being enabled, // so we look for the JSDoc instead of the inferred type. if (isValidDefineType(name.docInfo.getType())) { allDefines.add(name); } else { JSError error = JSError.make( name.declaration.sourceName, name.declaration.node, INVALID_DEFINE_TYPE_ERROR); compiler.report(error); } } else if (name.refs != null) { for (Ref ref : name.refs) { Node n = ref.node; Node parent = ref.node.getParent(); JSDocInfo info = n.getJSDocInfo(); if (info == null && parent.getType() == Token.VAR && parent.hasOneChild()) { info = parent.getJSDocInfo(); } if (info != null && info.isDefine()) { allDefines.add(name); break; } } } } CollectDefines pass = new CollectDefines(compiler, allDefines); NodeTraversal.traverse(compiler, root, pass); return pass.getAllDefines(); } /** * Finds all assignments to @defines, and figures out the last value of * the @define. */ private static final class CollectDefines implements Callback { private final AbstractCompiler compiler; private final Map<String, DefineInfo> assignableDefines; private final Map<String, DefineInfo> allDefines; private final Map<Node, RefInfo> allRefInfo; // A hack that allows us to remove ASSIGN/VAR statements when // we're currently visiting one of the children of the assign. private Node lvalueToRemoveLater = null; // A stack tied to the node traversal, to keep track of whether // we're in a conditional block. If 1 is at the top, assignment to // a define is allowed. Otherwise, it's not allowed. private final Deque<Integer> assignAllowed; CollectDefines(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>AbstractCompiler compiler, List<Name> listOfDefines) { this.compiler = compiler; this.allDefines = Maps.newHashMap(); assignableDefines = Maps.newHashMap(); assignAllowed = new ArrayDeque<Integer>(); assignAllowed.push(1); // Create a map of references to defines keyed by node for easy lookup allRefInfo = Maps.newHashMap(); for (Name name : listOfDefines) { if (name.declaration != null) { allRefInfo.put(name.declaration.node, new RefInfo(name.declaration, name)); } if (name.refs != null) { for (Ref ref : name.refs) { // If there's a TWIN def, only put one of the twins in. if (ref.getTwin() == null || !ref.getTwin().isSet()) { allRefInfo.put(ref.node, new RefInfo(ref, name)); } } } } } /** * Get a map of {@link DefineInfo} structures, keyed by the name of * the define. */ Map<String, DefineInfo> getAllDefines() { return allDefines; } /** * Keeps track of whether the traversal is in a conditional branch. * We traverse all nodes of the parse tree. */ public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { updateAssignAllowedStack(n, true); return true; } public void visit(NodeTraversal t, Node n, Node parent) { RefInfo refInfo = allRefInfo.get(n); if (refInfo != null) { Ref ref = refInfo.ref; Name name = refInfo.name; String fullName = name.fullName(); switch (ref.type) { case SET_FROM_GLOBAL: case SET_FROM_LOCAL: Node valParent = getValueParent(ref); Node val = valParent.getLastChild(); if (valParent.getType() == Token.ASSIGN && name.isSimpleName() && name.declaration == ref) { // For defines, it's an error if a simple name is assigned // before it's declared compiler.report( t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName)); } else if (processDefineAssignment(t, fullName, val, valParent)) { // remove the assignment so that the variable is still declared, // but no longer assigned to a value, e.g., // DEF_FOO = 5; // becomes "5;" // We can't remove the ASSIGN/VAR when we're still visiting its // children, so we'll have to come back later to remove it. refInfo.name.removeRef(ref); lvalueToRemoveLater = valParent; } break; default: if (t.inGlobalScope()) { // Treat this as a reference to a define in the global scope. // After this point, the define

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> must not be reassigned, // or it's an error. DefineInfo info = assignableDefines.get(fullName); if (info != null) { setDefineInfoNotAssignable(info, t); assignableDefines.remove(fullName); } } break; } } if (!t.inGlobalScope() && n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) { // warn about @define annotations in local scopes compiler.report( t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, "")); } if (lvalueToRemoveLater == n) { lvalueToRemoveLater = null; if (n.getType() == Token.ASSIGN) { Node last = n.getLastChild(); n.removeChild(last); parent.replaceChild(n, last); } else { Preconditions.checkState(n.getType() == Token.NAME); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.getType() == Token.CALL) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are not. But this would be overkill, expecially because // the intended use of defines is with config_files, where // all the defines are at the top of the bundle. for (DefineInfo info : assignableDefines.values()) { setDefineInfoNotAssignable(info, t); } assignableDefines.clear(); } } updateAssignAllowedStack(n, false); } /** * Determines whether assignment to a define should be allowed * in the subtree of the given node, and if not, records that fact. * * @param n The node whose subtree we're about to enter or exit. * @param entering True if we're entering the subtree, false otherwise. */ private void updateAssignAllowedStack(Node n, boolean entering) { switch (n.getType()) { case Token.CASE: case Token.FOR: case Token.FUNCTION: case Token.HOOK: case Token.IF: case Token.SWITCH: case Token.WHILE: if (entering) { assignAllowed.push(0); } else { assignAllowed.remove(); } break; } } /** * Determines whether assignment to a define should be allowed * at the current point of the traversal. */ private boolean isAssignAllowed() { return assignAllowed.element() == 1; } /** * Tracks the given define. * * @param t The

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> current traversal, for context. * @param name The full name for this define. * @param value The value assigned to the define. * @param valueParent The parent node of value. * @return Whether we should remove this assignment from the parse tree. */ private boolean processDefineAssignment(NodeTraversal t, String name, Node value, Node valueParent) { if (value == null || !NodeUtil.isValidDefineValue(value, allDefines.keySet())) { compiler.report( t.makeError(value, INVALID_DEFINE_INIT_ERROR, name)); } else if (!isAssignAllowed()) { compiler.report( t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name)); } else { DefineInfo info = allDefines.get(name); if (info == null) { // First declaration of this define. info = new DefineInfo(value, valueParent); allDefines.put(name, info); assignableDefines.put(name, info); } else if (info.recordAssignment(value)) { // The define was already initialized, but this is a safe // re-assignment. return true; } else { // The define was already initialized, and this is an unsafe // re-assignment. compiler.report( t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR, name, info.getReasonWhyNotAssignable())); } } return false; } /** * Gets the parent node of the value for any assignment to a Name. * For example, in the assignment * {@code var x = 3;} * the parent would be the NAME node. */ private static Node getValueParent(Ref ref) { // there are two types of declarations: VARs and ASSIGNs return ref.node.getParent() != null && ref.node.getParent().getType() == Token.VAR ? ref.node : ref.node.getParent(); } /** * Records the fact that because of the current node in the node traversal, * the define can't ever be assigned again. * * @param info Represents the define variable. * @param t The current traversal. */ private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) { info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE, t.getLineNumber(), t.getSourceName())); } /** * A simple data structure for associating a Ref with the name * that it references. */ private static class RefInfo { final Ref ref; final Name name; RefInfo(Ref ref, Name name) { this.ref = ref; this.name = name; } } } /** * A simple class for storing information about a define. * Gathers the initial value, the last assigned value, and whether * the define can be safely assigned a new value. */ private static final class DefineInfo {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> traversed, they will be visited by * {@link #visit(NodeTraversal, Node, Node)} in post order.</p> * <p>Implementations can have side effects (e.g. modifying the parse * tree).</p> * @return whether the children of this node should be visited */ boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent); /** * <p>Visits a node in post order (after its children have been visited). * A node is visited only if all its parents should be traversed * ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p> * <p>Implementations can have side effects (e.g. modifying the parse * tree).</p> */ void visit(NodeTraversal t, Node n, Node parent); } /** * Callback that also knows about scope changes */ public interface ScopedCallback extends Callback { /** * Called immediately after entering a new scope. The new scope can * be accessed through t.getScope() */ void enterScope(NodeTraversal t); /** * Called immediately before exiting a scope. The ending scope can * be accessed through t.getScope() */ void exitScope(NodeTraversal t); } /** * Abstract callback to visit all nodes in post order. */ public abstract static class AbstractPostOrderCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return true; } } /** * Abstract callback to visit all nodes but not traverse into function * bodies. */ public abstract static class AbstractShallowCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild(); } } /** * Abstract callback to visit all structure and statement nodes but doesn't * traverse into functions or expressions. */ public abstract static class AbstractShallowStatementCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isStatementBlock(parent); } } /** * Abstract callback to visit a pruned set of nodes. */ public abstract static class AbstractNodeTypePruningCallback implements Callback { private final Set<Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include/exclude in the traversal * @param

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (!sourceName.isEmpty()) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { sourceName = ""; curNode = root; pushScope(root); traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { if (n == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceName + ":" + lineNumber + ":" + columnNumber + "\n" + src + "\n"; } /** * Traverses a parse tree recursively with a scope, starting with the given * root. This should only be used in the global scope. Otherwise, use * {@link #traverseAtScope}. */ void traverseWithScope(Node root, Scope s) { Preconditions.checkState(s.isGlobal()); sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.getType() == Token.FUNCTION) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may be not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } /** * Gets the compiler. */ public Compiler getCompiler() { // TODO(nicksantos): Remove this type cast. This is just temporary // while refactoring. return (Compiler) compiler

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur != null) { int line = cur.getLineno(); if (line >=0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty, but not null */ public String getSourceName() { return sourceName; } /** * Gets the current input source. */ public CompilerInput getInput() { return compiler.getInput(sourceName); } /** * Gets the current input module. */ public JSModule getModule() { CompilerInput input = getInput(); return input == null ? null : input.getModule(); } /** Returns the node currently being traversed. */ public Node getCurrentNode() { return curNode; } /** * Traverses a node recursively. */ public static void traverse( AbstractCompiler compiler, Node root, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverse(root); } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ @SuppressWarnings("fallthrough") private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) return; switch (type) { case Token.FUNCTION: traverseFunction(n, parent); break; default: for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } break; } curNode = n; callback.visit(this, n, parent); } /** * Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getType() == Token.FUNCTION); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body Preconditions.checkState(body.getNext() == null && body.getType() == Token.BLOCK); traverseBranch(body, n); popScope(); } /** Examines the functions stack for the last instance of a function node. */ @SuppressWarnings("unchecked") public Node getEnclosingFunction() { if (scopes.size() + scopeRoots.size() < 2) { return null; } else { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Node node) { Preconditions.checkState(curNode != null); scopeRoots.push(node); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Scope s) { Preconditions.checkState(curNode != null); scopes.push(s); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Pops back to the previous scope (e.g. when leaving a function). */ private void popScope() { if (scopeCallback != null) { scopeCallback.exitScope(this); } if (scopeRoots.isEmpty()) { scopes.pop(); } else { scopeRoots.pop(); } cfgs.pop(); } /** Gets the current scope. */ public Scope getScope() { Scope scope = scopes.isEmpty() ? null : scopes.peek(); if (scopeRoots.isEmpty()) { return scope; } Iterator<Node> it = scopeRoots.descendingIterator(); while (it.hasNext()) { scope = scopeCreator.createScope(it.next(), scope); scopes.push(scope); } scopeRoots.clear(); return scope; } /** Gets the control flow graph for the current JS scope. */ public ControlFlowGraph<Node> getControlFlowGraph() { if (cfgs.peek() == null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true); cfa.process(null, getScopeRoot()); cfgs.pop(); cfgs.push(cfa.getCfg()); } return cfgs.peek(); } /** Returns the current scope's root. */ public Node getScopeRoot() { if (scopeRoots.isEmpty())

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> unless we can prove * that it modifies external state. This is similar to * {@code FlowSensitiveInlineVariables}, except that it works for variables * used across scopes. * */ class RemoveUnusedVars implements CompilerPass, OptimizeCalls.CallGraphCompilerPass { private final AbstractCompiler compiler; private final boolean removeGlobals; private boolean preserveFunctionExpressionNames; /** * Keep track of variables that we've referenced. */ private final Set<Var> referenced = Sets.newHashSet(); /** * Keep track of variables that might be unreferenced. */ private final List<Var> maybeUnreferenced = Lists.newArrayList(); /** * Keep track of scopes that we've traversed. */ private final List<Scope> allFunctionScopes = Lists.newArrayList(); /** * Keep track of assigns to variables that we haven't referenced. */ private final Multimap<Var, Assign> assignsByVar = ArrayListMultimap.create(); /** * The assigns, indexed by the NAME node that they assign to. */ private final Map<Node, Assign> assignsByNode = Maps.newHashMap(); /** * Keep track of continuations that are finished iff the variable they're * indexed by is referenced. */ private final Multimap<Var, Continuation> continuations = ArrayListMultimap.create(); private boolean modifyCallSites; private CallSiteOptimizer callSiteOptimizer; RemoveUnusedVars( AbstractCompiler compiler, boolean removeGlobals, boolean preserveFunctionExpressionNames, boolean modifyCallSites) { this.compiler = compiler; this.removeGlobals = removeGlobals; this.preserveFunctionExpressionNames = preserveFunctionExpressionNames; this.modifyCallSites = modifyCallSites; } /** * Traverses the root, removing all unused variables. Multiple traversals * may occur to ensure all unused variables are removed. */ public void process(Node externs, Node root) { SimpleDefinitionFinder defFinder = null; if (modifyCallSites) { // For testing, allow the SimpleDefinitionFinder to be build now. defFinder = new SimpleDefinitionFinder(compiler); defFinder.process(externs, root); } process(externs, root, defFinder); } @Override public void process( Node externs, Node root, SimpleDefinitionFinder defFinder) { if (modifyCallSites) { Preconditions.checkNotNull(defFinder); callSiteOptimizer = new CallSiteOptimizer(compiler, defFinder); } traverseAndRemoveUnusedReferences(root); } /** * Traverses a node recursively. Call this once per pass. */ private void traverseAndRemoveUnusedReferences(Node root) { Scope scope = new SyntacticScopeCreator(compiler).createScope(root, null); traverseNode(root, null, scope); if (removeGlobals) { collectMaybeUnreferencedVars(scope); } interpretAssigns(); removeUnreferencedVars(); for (Scope fnScope : allFunctionScopes) { removeUn

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>referencedFunctionArgs(fnScope); } } /** * Traverses everything in the current scope and marks variables that * are referenced. * * During traversal, we identify subtrees that will only be * referenced if their enclosing variables are referenced. Instead of * traversing those subtrees, we create a continuation for them, * and traverse them lazily. */ private void traverseNode(Node n, Node parent, Scope scope) { int type = n.getType(); Var var = null; switch (type) { case Token.FUNCTION: // If this function is a removable var, then create a continuation // for it instead of traversing immediately. if (NodeUtil.isFunctionDeclaration(n)) { var = scope.getVar(n.getFirstChild().getString()); } if (var != null && isRemovableVar(var)) { continuations.put(var, new Continuation(n, scope)); } else { traverseFunction(n, scope); } return; case Token.ASSIGN: Assign maybeAssign = Assign.maybeCreateAssign(n); if (maybeAssign != null) { // Put this in the assign map. It might count as a reference, // but we won't know that until we have an index of all assigns. var = scope.getVar(maybeAssign.nameNode.getString()); if (var != null) { assignsByVar.put(var, maybeAssign); assignsByNode.put(maybeAssign.nameNode, maybeAssign); if (isRemovableVar(var) && !maybeAssign.mayHaveSecondarySideEffects) { // If the var is unreferenced and performing this assign has // no secondary side effects, then we can create a continuation // for it instead of traversing immediately. continuations.put(var, new Continuation(n, scope)); return; } } } break; case Token.NAME: var = scope.getVar(n.getString()); if (parent.getType() == Token.VAR) { Node value = n.getFirstChild(); if (value != null && var != null && isRemovableVar(var) && !NodeUtil.mayHaveSideEffects(value)) { // If the var is unreferenced and creating its value has no side // effects, then we can create a continuation for it instead // of traversing immediately. continuations.put(var, new Continuation(n, scope)); return; } } else { // All name references that aren't declarations or assigns // are references to other vars. if (var != null) { // If that var hasn't already been marked referenced, then // start tracking it. If this is an assign, do nothing // for now. if (isRemovableVar(var)) { if (!assignsByNode.containsKey(n)) { markReferencedVar(var); } } else { markReferencedVar(var); }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } } break; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { traverseNode(c, n, scope); } } private boolean isRemovableVar(Var var) { // Global variables are off-limits if the user might be using them. if (!removeGlobals && var.isGlobal()) { return false; } // Referenced variables are off-limits. if (referenced.contains(var)) { return false; } // Exported variables are off-limits. if (compiler.getCodingConvention().isExported(var.getName())) { return false; } return true; } /** * Traverses a function, which creates a new scope in javascript. * * Note that CATCH blocks also create a new scope, but only for the * catch variable. Declarations within the block actually belong to the * enclosing scope. Because we don't remove catch variables, there's * no need to treat CATCH blocks differently like we do functions. */ private void traverseFunction(Node n, Scope parentScope) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getType() == Token.FUNCTION); final Node body = n.getLastChild(); Preconditions.checkState(body.getNext() == null && body.getType() == Token.BLOCK); Scope fnScope = new SyntacticScopeCreator(compiler).createScope(n, parentScope); traverseNode(body, n, fnScope); collectMaybeUnreferencedVars(fnScope); allFunctionScopes.add(fnScope); } /** * For each variable in this scope that we haven't found a reference * for yet, add it to the list of variables to check later. */ private void collectMaybeUnreferencedVars(Scope scope) { for (Iterator<Var> it = scope.getVars(); it.hasNext(); ) { Var var = it.next(); if (isRemovableVar(var)) { maybeUnreferenced.add(var); } } } /** * Removes unreferenced arguments from a function declaration and when * possible the function's callSites. * * @param fnScope The scope inside the function */ private void removeUnreferencedFunctionArgs(Scope fnScope) { // TODO(johnlenz): Update type registry for function signature changes. Node function = fnScope.getRootNode(); Preconditions.checkState(function.getType() == Token.FUNCTION); Node argList = getFunctionArgList(function); boolean modifyCallers = modifyCallSites && callSiteOptimizer.canModifyCallers(function); if (!modifyCallers) { // Strip unreferenced args off the end of the function declaration. Node lastArg; while ((lastArg = argList.getLastChild()) != null) { Var var = fnScope.getVar(lastArg.getString()); if (!referenced.contains(var)) { Preconditions.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>checkNotNull(var == null); argList.removeChild(lastArg); compiler.reportCodeChange(); } else { break; } } } else { callSiteOptimizer.optimize(fnScope, referenced); } } /** * @return the LP node containing the function parameters. */ private static Node getFunctionArgList(Node function) { return function.getFirstChild().getNext(); } private static class CallSiteOptimizer { private final AbstractCompiler compiler; private final SimpleDefinitionFinder defFinder; CallSiteOptimizer( AbstractCompiler compiler, SimpleDefinitionFinder defFinder) { this.compiler = compiler; this.defFinder = defFinder; } public void optimize(Scope fnScope, Set<Var> referenced) { Node function = fnScope.getRootNode(); Preconditions.checkState(function.getType() == Token.FUNCTION); Node argList = getFunctionArgList(function); // In this path we try to modify all the call sites to remove unused // function parameters. boolean changeCallSignature = canChangeSignature(function); removeUnreferencedFunctionArgs( fnScope, function, referenced, argList.getFirstChild(), 0, changeCallSignature); } /** * For each unused function parameter, determine if it can be removed * from all the call sites, if so, remove it from the function signature * and the call sites otherwise replace the unused value where possible * with a constant (0). * * @param scope The function scope * @param function The function * @param param The current parameter node in the parameter list. * @param paramIndex The index of the current parameter * @param canChangeSignature Whether function signature can be change. * @return Whether there is a following function parameter. */ private boolean removeUnreferencedFunctionArgs( Scope scope, Node function, Set<Var> referenced, Node param, int paramIndex, boolean canChangeSignature) { if (param != null) { // Take care of the following siblings first. boolean hasFollowing = removeUnreferencedFunctionArgs( scope, function, referenced, param.getNext(), paramIndex+1, canChangeSignature); Var var = scope.getVar(param.getString()); if (!referenced.contains(var)) { Preconditions.checkNotNull(var); // Remove call parameter if we can generally change the signature // or if it is the last parameter in the parameter list. boolean modifyAllCallSites = canChangeSignature || !hasFollowing; if (modifyAllCallSites) { modifyAllCallSites = canRemoveArgFromCallSites( function, paramIndex); } tryRemoveArgFromCallSites(function, paramIndex, modifyAllCallSites); // Remove an unused function parameter if all the call sites can // be modified to remove it, or if it is the last parameter. if (modifyAllCallSites || !hasFollowing) { getFunctionArgList(function).removeChild(param); compiler.reportCodeChange(); return hasFollowing; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } return true; } else { // Anything past the last formal parameter can be removed from the call // sites. tryRemoveAllFollowingArgs(function, paramIndex-1); return false; } } /** * Remove all references to a parameter, otherwise simplify the known * references. * @return Whether all the references were removed. */ private boolean canRemoveArgFromCallSites(Node function, int argIndex) { Definition definition = getFunctionDefinition(function); // Check all the call sites. for (UseSite site : defFinder.getUseSites(definition)) { if (isModifableCallSite(site)) { Node arg = NodeUtil.getArgumentForCallOrNew( site.node.getParent(), argIndex); // TODO(johnlenz): try to remove parameters with side-effects by // decomposing the call expression. if (arg != null && NodeUtil.mayHaveSideEffects(arg, compiler)) { return false; } } else { return false; } } return true; } /** * Remove all references to a parameter if possible otherwise simplify the * side-effect free parameters. */ private void tryRemoveArgFromCallSites( Node function, int argIndex, boolean canModifyAllSites) { Definition definition = getFunctionDefinition(function); for (UseSite site : defFinder.getUseSites(definition)) { if (isModifableCallSite(site)) { Node arg = NodeUtil.getArgumentForCallOrNew( site.node.getParent(), argIndex); if (arg != null) { Node argParent = arg.getParent(); // Even if we can't change the signature in general we can always // remove an unused value off the end of the parameter list. if (canModifyAllSites || (arg.getNext() == null && !NodeUtil.mayHaveSideEffects(arg, compiler))) { // Remove the arg completely argParent.removeChild(arg); compiler.reportCodeChange(); } else { // replace the node in the arg with 0 if (!NodeUtil.mayHaveSideEffects(arg, compiler) && (arg.getType() != Token.NUMBER || arg.getDouble() != 0)) { argParent.replaceChild( arg, Node.newNumber(0).copyInformationFrom(arg)); compiler.reportCodeChange(); } } } } } } /** * Remove all the following parameters without side-effects */ private void tryRemoveAllFollowingArgs(Node function, final int argIndex) { Definition definition = getFunctionDefinition(function); for (UseSite site : defFinder.getUseSites(definition)) { if (!isModifableCallSite(site)) { continue; } Node arg = NodeUtil.getArgumentForCallOrNew( site.node.getParent(), argIndex); while (arg != null) { Node next = arg.getNext(); if (next

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> != null && !NodeUtil.mayHaveSideEffects(next)) { arg.getParent().removeChildAfter(arg); compiler.reportCodeChange(); } else { arg = next; } } } } /** * @param function * @return Whether the callers to this function can be modified in any way. */ boolean canModifyCallers(Node function) { if (NodeUtil.isVarArgsFunction(function)) { return false; } DefinitionSite defSite = defFinder.getDefinitionForFunction(function); if (defSite == null) { return false; } Definition definition = defSite.definition; // Be conservative, don't try to optimize any declaration that isn't as // simple function declaration or assignment. if (!SimpleDefinitionFinder.isSimpleFunctionDeclaration(function)) { return false; } // Assume an exported method result is used, and the definition might be // changed. if (SimpleDefinitionFinder.maybeExported(compiler, definition)) { return false; } Collection<UseSite> useSites = defFinder.getUseSites(definition); for (UseSite site : useSites) { // Multiple definitions prevent rewrite. // TODO(johnlenz): Allow rewrite all definitions are valid. Node nameNode = site.node; Collection<Definition> singleSiteDefinitions = defFinder.getDefinitionsReferencedAt(nameNode); if (singleSiteDefinitions.size() > 1) { return false; } Preconditions.checkState(!singleSiteDefinitions.isEmpty()); Preconditions.checkState(singleSiteDefinitions.contains(definition)); } return true; } /** * @param site The site to inspect * @return Whether the call site is suitable for modification */ private static boolean isModifableCallSite(UseSite site) { return SimpleDefinitionFinder.isCallOrNewSite(site) && !NodeUtil.isFunctionObjectCallOrApply(site.node.getParent()); } /** * @return Whether the definitionSite represents a function whose call * signature can be modified. */ private boolean canChangeSignature(Node function) { Definition definition = getFunctionDefinition(function); Preconditions.checkState(!definition.isExtern()); Collection<UseSite> useSites = defFinder.getUseSites(definition); for (UseSite site : useSites) { // Accessing the property directly prevents rewrite. if (!SimpleDefinitionFinder.isCallOrNewSite(site)) { return false; } // TODO(johnlenz): support .call signature changes. if (NodeUtil.isFunctionObjectCallOrApply(site.node.getParent())) { return false; } // TODO(johnlenz): support specialization // Multiple definitions prevent rewrite. // Attempt to validate the state of the simple definition finder. Node nameNode = site.node; Collection<Definition> singleSiteDefinitions = defFinder.getDefinitionsReferencedAt(nameNode); Preconditions.checkState(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>singleSiteDefinitions.size() == 1); Preconditions.checkState(singleSiteDefinitions.contains(definition)); } return true; } /** * @param function * @return the Definition object for the function. */ private Definition getFunctionDefinition(Node function) { DefinitionSite definitionSite = defFinder.getDefinitionForFunction( function); Preconditions.checkNotNull(definitionSite); Definition definition = definitionSite.definition; Preconditions.checkState(!definitionSite.inExterns); Preconditions.checkState(definition.getRValue() == function); return definition; } } /** * Look at all the property assigns to all variables. * These may or may not count as references. For example, * * <code> * var x = {}; * x.foo = 3; // not a reference. * var y = foo(); * y.foo = 3; // is a reference. * </code> * * Interpreting assigments could mark a variable as referenced that * wasn't referenced before, in order to keep it alive. Because we find * references by lazily traversing subtrees, marking a variable as * referenced could trigger new traversals of new subtrees, which could * find new references. * * Therefore, this interpretation needs to be run to a fixed point. */ private void interpretAssigns() { boolean changes = false; do { changes = false; // We can't use traditional iterators and iterables for this list, // because our lazily-evaluated continuations will modify it while // we traverse it. for (int current = 0; current < maybeUnreferenced.size(); current++) { Var var = maybeUnreferenced.get(current); if (referenced.contains(var)) { maybeUnreferenced.remove(current); current--; } else { boolean assignedToUnknownValue = false; boolean hasPropertyAssign = false; if (var.getParentNode().getType() == Token.VAR && !NodeUtil.isForIn(var.getParentNode().getParent())) { Node value = var.getInitialValue(); assignedToUnknownValue = value != null && !NodeUtil.isLiteralValue(value, true); } else { // This was initialized to a function arg or a catch param // or a for...in variable. assignedToUnknownValue = true; } for (Assign assign : assignsByVar.get(var)) { if (assign.isPropertyAssign) { hasPropertyAssign = true; } else if (!NodeUtil.isLiteralValue( assign.assignNode.getLastChild(), true)) { assignedToUnknownValue = true; } } if (assignedToUnknownValue && hasPropertyAssign) { changes = markReferencedVar(var) || changes; maybeUnreferenced.remove(current); current--; } } } } while (changes); } /** * Remove all assigns to a

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> var. */ private void removeAllAssigns(Var var) { for (Assign assign : assignsByVar.get(var)) { assign.remove(); compiler.reportCodeChange(); } } /** * Marks a var as referenced, recursing into any values of this var * that we skipped. * @return True if this variable had not been referenced before. */ private boolean markReferencedVar(Var var) { if (referenced.add(var)) { for (Continuation c : continuations.get(var)) { c.apply(); } return true; } return false; } /** * Removes any vars in the scope that were not referenced. Removes any * assigments to those variables as well. */ private void removeUnreferencedVars() { CodingConvention convention = compiler.getCodingConvention(); for (Iterator<Var> it = maybeUnreferenced.iterator(); it.hasNext(); ) { Var var = it.next(); // Regardless of what happens to the original declaration, // we need to remove all assigns, because they may contain references // to other unreferenced variables. removeAllAssigns(var); compiler.addToDebugLog("Unreferenced var: " + var.name); Node nameNode = var.nameNode; Node toRemove = nameNode.getParent(); Node parent = toRemove.getParent(); Preconditions.checkState( toRemove.getType() == Token.VAR || toRemove.getType() == Token.FUNCTION || toRemove.getType() == Token.LP && parent.getType() == Token.FUNCTION, "We should only declare vars and functions and function args"); if (toRemove.getType() == Token.LP && parent.getType() == Token.FUNCTION) { // Don't remove function arguments here. That's a special case // that's taken care of in removeUnreferencedFunctionArgs. } else if (NodeUtil.isFunctionExpression(toRemove)) { if (!preserveFunctionExpressionNames) { toRemove.getFirstChild().setString(""); compiler.reportCodeChange(); } // Don't remove bleeding functions. } else if (parent != null && parent.getType() == Token.FOR && parent.getChildCount() < 4) { // foreach iterations have 3 children. Leave them alone. } else if (toRemove.getType() == Token.VAR && nameNode.hasChildren() && NodeUtil.mayHaveSideEffects(nameNode.getFirstChild())) { // If this is a single var declaration, we can at least remove the // declaration itself and just leave the value, e.g., // var a = foo(); => foo(); if (toRemove.getChildCount() == 1) { parent.replaceChild(toRemove, new Node(Token.EXPR_RESULT, nameNode.removeFirstChild())); compiler.reportCodeChange(); } } else if (toRemove.getType() == Token.VAR && toRemove.getChildCount

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>() > 1) { // For var declarations with multiple names (i.e. var a, b, c), // only remove the unreferenced name toRemove.removeChild(nameNode); compiler.reportCodeChange(); } else if (parent != null) { NodeUtil.removeChild(parent, toRemove); compiler.reportCodeChange(); } } } /** * Our progress in a traversal can be expressed completely as the * current node and scope. The continuation lets us save that * information so that we can continue the traversal later. */ private class Continuation { private final Node node; private final Scope scope; Continuation(Node node, Scope scope) { this.node = node; this.scope = scope; } void apply() { if (NodeUtil.isFunctionDeclaration(node)) { traverseFunction(node, scope); } else { for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { traverseNode(child, node, scope); } } } } private static class Assign { final Node assignNode; final Node nameNode; // If false, then this is an assign to the normal variable. Otherwise, // this is an assign to a property of that variable. final boolean isPropertyAssign; // Secondary side effects are any side effects in this assign statement // that aren't caused by the assignment operation itself. For example, // a().b = 3; // a = b(); // var foo = (a = b); // In the first two cases, the sides of the assignment have side-effects. // In the last one, the result of the assignment is used, so we // are conservative and assume that it may be used in a side-effecting // way. final boolean mayHaveSecondarySideEffects; Assign(Node assignNode, Node nameNode, boolean isPropertyAssign) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); this.assignNode = assignNode; this.nameNode = nameNode; this.isPropertyAssign = isPropertyAssign; this.mayHaveSecondarySideEffects = assignNode.getParent().getType() != Token.EXPR_RESULT || NodeUtil.mayHaveSideEffects(assignNode.getFirstChild()) || NodeUtil.mayHaveSideEffects(assignNode.getLastChild()); } /** * If this is an assign to a variable or its property, return it. * Otherwise, return null. */ static Assign maybeCreateAssign(Node assignNode) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); // Skip one level of GETPROPs or GETELEMs. // // Don't skip more than one level, because then we get into // situations where assigns to properties of properties will always // trigger side-effects, and the variable they're on cannot be removed. boolean isPropAssign = false; Node current = assignNode.getFirstChild();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (NodeUtil.isGet(current)) { current = current.getFirstChild(); isPropAssign = true; if (current.getType() == Token.GETPROP && current.getLastChild().getString().equals("prototype")) { // Prototype properties sets should be considered like normal // property sets. current = current.getFirstChild(); } } if (current.getType() == Token.NAME) { return new Assign(assignNode, current, isPropAssign); } return null; } /** * Replace the current assign with its right hand side. */ void remove() { Node parent = assignNode.getParent(); if (mayHaveSecondarySideEffects) { Node replacement = assignNode.getLastChild().detachFromParent(); // Aggregate any expressions in GETELEMs. for (Node current = assignNode.getFirstChild(); current.getType() != Token.NAME; current = current.getFirstChild()) { if (current.getType() == Token.GETELEM) { replacement = new Node(Token.COMMA, current.getLastChild().detachFromParent(), replacement); replacement.copyInformationFrom(current); } } parent.replaceChild(assignNode, replacement); } else { Node gramps = parent.getParent(); if (parent.getType() == Token.EXPR_RESULT) { gramps.removeChild(parent); } else { parent.replaceChild(assignNode, assignNode.getLastChild().detachFromParent()); } } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> removeTryCatchFinally = false; closurePass = false; rewriteNewDateGoogNow = true; removeAbstractMethods = true; removeClosureAsserts = false; stripTypes = Collections.emptySet(); stripNameSuffixes = Collections.emptySet(); stripNamePrefixes = Collections.emptySet(); stripTypePrefixes = Collections.emptySet(); customPasses = null; markNoSideEffectCalls = false; defineReplacements = Maps.newHashMap(); moveFunctionDeclarations = false; instrumentationTemplate = null; appNameStr = ""; recordFunctionInformation = false; generateExports = false; cssRenamingMap = null; processObjectPropertyString = false; idGenerators = Collections.emptySet(); replaceStringsFunctionDescriptions = Collections.emptyList(); replaceStringsPlaceholderToken = ""; // Output printInputDelimiter = false; prettyPrint = false; lineBreak = false; reportPath = null; tracer = TracerMode.OFF; colorizeErrorOutput = false; errorFormat = ErrorFormat.SINGLELINE; warningsGuard = null; debugFunctionSideEffectsPath = null; jsOutputFile = ""; externExports = false; nameReferenceReportPath = null; nameReferenceGraphPath = null; } /** * Returns the map of define replacements. */ public Map<String, Node> getDefineReplacements() { Map<String, Node> map = Maps.newHashMap(); for (Map.Entry<String, Object> entry : defineReplacements.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof Boolean) { map.put(name, ((Boolean) value).booleanValue() ? new Node(Token.TRUE) : new Node(Token.FALSE)); } else if (value instanceof Integer) { map.put(name, Node.newNumber(((Integer) value).intValue())); } else if (value instanceof Double) { map.put(name, Node.newNumber(((Double) value).doubleValue())); } else { Preconditions.checkState(value instanceof String); map.put(name, Node.newString((String) value)); } } return map; } /** * Sets the value of the {@code @define} variable in JS * to a boolean literal. */ public void setDefineToBooleanLiteral(String defineName, boolean value) { defineReplacements.put(defineName, new Boolean(value)); } /** * Sets the value of the {@code @define} variable in JS to a * String literal. */ public void setDefineToStringLiteral(String defineName, String value) { defineReplacements.put(defineName, value); } /** * Sets the value of the {@code @define} variable in JS to a * number literal. */ public void setDefineToNumberLiteral(String defineName, int value) { defineReplacements.put(defineName, new Integer(value)); } /**

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> setRemoveAbstractMethods(boolean remove) { this.removeAbstractMethods = remove; } public void setRemoveClosureAsserts(boolean remove) { this.removeClosureAsserts = remove; } /** * If true, name anonymous functions only. All other passes will be skipped. */ public void setNameAnonymousFunctionsOnly(boolean value) { this.nameAnonymousFunctionsOnly = value; } public void setColorizeErrorOutput(boolean colorizeErrorOutput) { this.colorizeErrorOutput = colorizeErrorOutput; } public boolean shouldColorizeErrorOutput() { return colorizeErrorOutput; } /** * If true, chain calls to functions that return this. */ public void setChainCalls(boolean value) { this.chainCalls = value; } /** * Enable runtime type checking, which adds JS type assertions for debugging. * * @param logFunction A JS function to be used for logging runtime type * assertion failures. */ public void enableRuntimeTypeCheck(String logFunction) { this.runtimeTypeCheck = true; this.runtimeTypeCheckLogFunction = logFunction; } public void disableRuntimeTypeCheck() { this.runtimeTypeCheck = false; } public void setCodingConvention(CodingConvention codingConvention) { this.codingConvention = codingConvention; } public CodingConvention getCodingConvention() { return codingConvention; } /** * Sort inputs by their goog.provide/goog.require calls, and prune inputs * whose symbols are not required. */ public void setManageClosureDependencies(boolean newVal) { manageClosureDependencies = newVal; } /** * Sort inputs by their goog.provide/goog.require calls. * * @param entryPoints Entry points to the program. Must be goog.provide'd * symbols. Any goog.provide'd symbols that are not a transitive * dependency of the entry points will be deleted. * Files without goog.provides, and their dependencies, * will always be left in. */ public void setManageClosureDependencies(List<String> entryPoints) { Preconditions.checkNotNull(entryPoints); manageClosureDependencies = true; manageClosureDependenciesEntryPoints = entryPoints; } /** * Controls how detailed the compilation summary is. Values: * 0 (never print summary), 1 (print summary only if there are * errors or warnings), 2 (print summary if type checking is on, * see --check_types), 3 (always print summary). The default level * is 1 */ public void setSummaryDetailLevel(int summaryDetailLevel) { this.summaryDetailLevel = summaryDetailLevel; } public void enableExternExports(boolean enable) { this.externExports = enable; } public boolean isExternExportsEnabled() { return externExports; } /** * Sets the output charset by name. */ public void setOutputCharset(String charsetName) { this.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * An AST generated totally by the compiler. * * @author nicksantos@google.com (Nick Santos) */ class SyntheticAst implements SourceAst { private static final long serialVersionUID = 1L; private final String sourceName; private Node root; SyntheticAst(String sourceName) { this.sourceName = sourceName; clearAst(); } @Override public Node getAstRoot(AbstractCompiler compiler) { return root; } @Override public void clearAst() { root = new Node(Token.SCRIPT); root.putProp(Node.SOURCENAME_PROP, sourceName); } @Override public SourceFile getSourceFile() { return null; } @Override public void setSourceFile(SourceFile file) { throw new IllegalStateException( "Cannot set a source file for a synthetic AST"); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>process(externs, root); compiler.setNormalized(); } /** * Propagate constant annotations over the Var graph. */ static class PropagateConstantAnnotationsOverVars extends AbstractPostOrderCallback implements CompilerPass { private final AbstractCompiler compiler; private final boolean assertOnChange; PropagateConstantAnnotationsOverVars( AbstractCompiler compiler, boolean forbidChanges) { this.compiler = compiler; this.assertOnChange = forbidChanges; } @Override public void process(Node externs, Node root) { new NodeTraversal(compiler, this).traverseRoots(externs, root); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Note: Constant properties annotations are not propagated. if (n.getType() == Token.NAME) { if (n.getString().isEmpty()) { return; } JSDocInfo info = null; // Find the JSDocInfo for a top level variable. Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } boolean shouldBeConstant = (info != null && info.isConstant()) || NodeUtil.isConstantByConvention( compiler.getCodingConvention(), n, parent); boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (shouldBeConstant && !isMarkedConstant) { if (assertOnChange) { String name = n.getString(); throw new IllegalStateException( "Unexpected const change.\n" + " name: "+ name + "\n" + " parent:" + n.getParent().toStringTree()); } n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } } /** * Walk the AST tree and verify that constant names are used consistently. */ static class VerifyConstants extends AbstractPostOrderCallback implements CompilerPass { final private AbstractCompiler compiler; final private boolean checkUserDeclarations; VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) { this.compiler = compiler; this.checkUserDeclarations = checkUserDeclarations; } @Override public void process(Node externs, Node root) { Node externsAndJs = root.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState(externsAndJs.hasChild(externs)); NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); } private Map<String, Boolean> constantMap = Maps.newHashMap(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { String name = n.getString(); if (n.getString().isEmpty()) { return; } boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (checkUserDeclarations) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> boolean expectedConst = false; CodingConvention convention = compiler.getCodingConvention(); if (NodeUtil.isConstantName(n) || NodeUtil.isConstantByConvention(convention, n, parent)) { expectedConst = true; } else { expectedConst = false; JSDocInfo info = null; Var var = t.getScope().getVar(n.getString()); if (var != null) { info = var.getJSDocInfo(); } if (info != null && info.isConstant()) { expectedConst = true; } else { expectedConst = false; } } if (expectedConst) { Preconditions.checkState(expectedConst == isConst, "The name " + name + " is not annotated as constant."); } else { Preconditions.checkState(expectedConst == isConst, "The name " + name + " should not be annotated as constant."); } } Boolean value = constantMap.get(name); if (value == null) { constantMap.put(name, isConst); } else { Preconditions.checkState(value.booleanValue() == isConst, "The name " + name + " is not consistently annotated as " + "constant."); } } } } /** * Simplify the AST: * - VAR declarations split, so they represent exactly one child * declaration. * - WHILEs are converted to FORs * - FOR loop are initializers are moved out of the FOR structure * - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are * moved into a block. * - Add constant annotations based on coding convention. */ static class NormalizeStatements implements Callback { private final AbstractCompiler compiler; private final boolean assertOnChange; NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) { this.compiler = compiler; this.assertOnChange = assertOnChange; } private void reportCodeChange(String changeDescription) { if (assertOnChange) { throw new IllegalStateException( "Normalize constraints violated:\n" + changeDescription); } compiler.reportCodeChange(); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { doStatementNormalizations(t, n, parent); return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.WHILE: if (CONVERT_WHILE_TO_FOR) { Node expr = n.getFirstChild(); n.setType(Token.FOR); Node empty = new Node(Token.EMPTY); empty.copyInformationFrom(n); n.addChildBefore(empty, expr); n.addChildAfter(empty.cloneNode(), expr); reportCodeChange("WHILE node"); } break; case Token.FUNCTION: normalizeFunctionDeclaration(n); break; case Token

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.NAME: case Token.STRING: case Token.GET: case Token.SET: annotateConstantsByConvention(n, parent); break; } } /** * Mark names and properties that are constants by convention. */ private void annotateConstantsByConvention(Node n, Node parent) { Preconditions.checkState( n.getType() == Token.NAME || n.getType() == Token.STRING || n.getType() == Token.GET || n.getType() == Token.SET); // There are only two cases where a string token // may be a variable reference: The right side of a GETPROP // or an OBJECTLIT key. boolean isObjLitKey = NodeUtil.isObjectLitKey(n, parent); boolean isProperty = isObjLitKey || (parent.getType() == Token.GETPROP && parent.getLastChild() == n); if (n.getType() == Token.NAME || isProperty) { boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME); if (!isMarkedConstant && NodeUtil.isConstantByConvention( compiler.getCodingConvention(), n, parent)) { if (assertOnChange) { String name = n.getString(); throw new IllegalStateException( "Unexpected const change.\n" + " name: "+ name + "\n" + " parent:" + n.getParent().toStringTree()); } n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } /** * Rewrite named unhoisted functions declarations to a known * consistent behavior so we don't to different logic paths for the same * code. From: * function f() {} * to: * var f = function () {}; */ private void normalizeFunctionDeclaration(Node n) { Preconditions.checkState(n.getType() == Token.FUNCTION); if (!NodeUtil.isFunctionExpression(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { rewriteFunctionDeclaration(n); } } /** * Rewrite the function declaration from: * function x() {} * FUNCTION * NAME * LP * BLOCK * to: * var x = function() {}; * VAR * NAME * FUNCTION * NAME (w/ empty string) * LP * BLOCK */ private void rewriteFunctionDeclaration(Node n) { // Prepare a spot for the function. Node oldNameNode = n.getFirstChild(); Node fnNameNode = oldNameNode.cloneNode(); Node var = new Node(Token.VAR, fnNameNode, n.getLineno(), n.getCharno()); var.copyInformationFrom(n); // Prepare the function oldNameNode.setString(""); // Move the function Node parent = n.getParent(); parent.replaceChild(n, var); fnNameNode.addChildToFront(n); reportCodeChange

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>("Function declaration"); } /** * Do normalizations that introduce new siblings or parents. */ private void doStatementNormalizations( NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.LABEL) { normalizeLabels(n); } // Only inspect the children of SCRIPTs, BLOCKs and LABELs, as all these // are the only legal place for VARs and FOR statements. if (NodeUtil.isStatementBlock(n) || n.getType() == Token.LABEL) { extractForInitializer(n, null, null); } // Only inspect the children of SCRIPTs, BLOCKs, as all these // are the only legal place for VARs. if (NodeUtil.isStatementBlock(n)) { splitVarDeclarations(n); } if (n.getType() == Token.FUNCTION) { moveNamedFunctions(n.getLastChild()); } } // TODO(johnlenz): Move this to NodeTypeNormalizer once the unit tests are // fixed. /** * Limit the number of special cases where LABELs need to be handled. Only * BLOCK and loops are allowed to be labeled. Loop labels must remain in * place as the named continues are not allowed for labeled blocks. */ private void normalizeLabels(Node n) { Preconditions.checkArgument(n.getType() == Token.LABEL); Node last = n.getLastChild(); switch (last.getType()) { case Token.LABEL: case Token.BLOCK: case Token.FOR: case Token.WHILE: case Token.DO: return; default: Node block = new Node(Token.BLOCK); block.copyInformationFrom(last); n.replaceChild(last, block); block.addChildToFront(last); reportCodeChange("LABEL normalization"); return; } } /** * Bring the initializers out of FOR loops. These need to be placed * before any associated LABEL nodes. This needs to be done from the top * level label first so this is called as a pre-order callback (from * shouldTraverse). * * @param n The node to inspect. * @param before The node to insert the initializer before. * @param beforeParent The parent of the node before which the initializer * will be inserted. */ private void extractForInitializer( Node n, Node before, Node beforeParent) { for (Node next, c = n.getFirstChild(); c != null; c = next) { next = c.getNext(); Node insertBefore = (before == null) ? c : before; Node insertBeforeParent = (before == null) ? n : beforeParent; switch (c.getType()) { case Token.LABEL: extractForInitializer(c, insertBefore, insertBeforeParent); break; case Token.FOR: if (NodeUtil.isForIn(c)) { Node first = c.getFirstChild(); if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>first.getType() == Token.VAR) { // Transform: // for (var a in b) {} // to: // var a; for (a in b) {}; Node newStatement = first.cloneTree(); Node name = first.removeFirstChild(); first.getParent().replaceChild(first, name); insertBeforeParent.addChildBefore(newStatement, insertBefore); reportCodeChange("FOR-IN var declaration"); } } else if (c.getFirstChild().getType() != Token.EMPTY) { Node init = c.getFirstChild(); Node empty = new Node(Token.EMPTY); empty.copyInformationFrom(c); c.replaceChild(init, empty); Node newStatement; // Only VAR statements, and expressions are allowed, // but are handled differently. if (init.getType() == Token.VAR) { newStatement = init; } else { newStatement = NodeUtil.newExpr(init); } insertBeforeParent.addChildBefore(newStatement, insertBefore); reportCodeChange("FOR initializer"); } break; } } } /** * Split a var node such as: * var a, b; * into individual statements: * var a; * var b; * @param n The whose children we should inspect. */ private void splitVarDeclarations(Node n) { for (Node next, c = n.getFirstChild(); c != null; c = next) { next = c.getNext(); if (c.getType() == Token.VAR) { if (assertOnChange && !c.hasChildren()) { throw new IllegalStateException("Empty VAR node."); } while (c.getFirstChild() != c.getLastChild()) { Node name = c.getFirstChild(); c.removeChild(name); Node newVar = new Node( Token.VAR, name, n.getLineno(), n.getCharno()); n.addChildBefore(newVar, c); reportCodeChange("VAR with multiple children"); } } } } /** * Move all the functions that are valid at the execution of the first * statement of the function to the beginning of the function definition. */ private void moveNamedFunctions(Node functionBody) { Preconditions.checkState( functionBody.getParent().getType() == Token.FUNCTION); Node previous = null; Node current = functionBody.getFirstChild(); // Skip any declarations at the beginning of the function body, they // are already in the right place. while (current != null && NodeUtil.isFunctionDeclaration(current)) { previous = current; current = current.getNext(); } // Find any remaining declarations and move them. Node insertAfter = previous; while (current != null) { // Save off the next node as the current node maybe removed. Node next = current.getNext(); if (NodeUtil.isFunctionDeclaration(current)) { // Remove the declaration from the body. Preconditions

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.checkNotNull(previous); functionBody.removeChildAfter(previous); // Readd the function at the top of the function body (after any // previous declarations). insertAfter = addToFront(functionBody, current, insertAfter); reportCodeChange("Move function declaration not at top of function"); } else { // Update the previous only if the current node hasn't been moved. previous = current; } current = next; } } /** * @param after The child node to insert the newChild after, or null if * newChild should be added to the front of parent's child list. * @return The inserted child node. */ private Node addToFront(Node parent, Node newChild, Node after) { if (after == null) { parent.addChildToFront(newChild); } else { parent.addChildAfter(newChild, after); } return newChild; } } /** * Remove duplicate VAR declarations. */ private void removeDuplicateDeclarations(Node externs, Node root) { Callback tickler = new ScopeTicklingCallback(); ScopeCreator scopeCreator = new SyntacticScopeCreator( compiler, new DuplicateDeclarationHandler()); NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator); t.traverseRoots(externs, root); } /** * ScopeCreator duplicate declaration handler. */ private final class DuplicateDeclarationHandler implements SyntacticScopeCreator.RedeclarationHandler { private Set<Var> hasOkDuplicateDeclaration = Sets.newHashSet(); /** * Remove duplicate VAR declarations encountered discovered during * scope creation. */ @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Preconditions.checkState(n.getType() == Token.NAME); Node parent = n.getParent(); Var v = s.getVar(name); if (v != null && s.isGlobal()) { // We allow variables to be duplicate declared if one // declaration appears in source and the other in externs. // This deals with issues where a browser built-in is declared // in one browser but not in another. if (v.isExtern() && !input.isExtern()) { if (hasOkDuplicateDeclaration.add(v)) { return; } } } // If name is "arguments", Var maybe null. if (v != null && v.getParentNode().getType() == Token.CATCH) { // Redeclaration of a catch expression variable is hard to model // without support for "with" expressions. // The EcmaScript spec (section 12.14), declares that a catch // "catch (e) {}" is handled like "with ({'e': e}) {}" so that // "var e" would refer to the scope variable, but any following // reference would still refer to "e" of the catch expression. // Until we have support for this disallow it. // Currently the Scope object

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> adds the catch expression to the // function scope, which is technically not true but a good // approximation for most uses. // TODO(johnlenz): Consider improving how scope handles catch // expression. // Use the name of the var before it was made unique. name = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName( name); compiler.report( JSError.make( input.getName(), n, CATCH_BLOCK_VAR_ERROR, name)); } else if (v != null && parent.getType() == Token.FUNCTION) { if (v.getParentNode().getType() == Token.VAR) { s.undeclare(v); s.declare(name, n, n.getJSType(), v.input); replaceVarWithAssignment(v.getNameNode(), v.getParentNode(), v.getParentNode().getParent()); } } else if (parent.getType() == Token.VAR) { Preconditions.checkState(parent.hasOneChild()); replaceVarWithAssignment(n, parent, parent.getParent()); } } /** * Remove the parent VAR. There are three cases that need to be handled: * 1) "var a = b;" which is replaced with "a = b" * 2) "label:var a;" which is replaced with "label:;". Ideally, the * label itself would be removed but that is not possible in the * context in which "onRedeclaration" is called. * 3) "for (var a in b) ..." which is replaced with "for (a in b)..." * Cases we don't need to handle are VARs with multiple children, * which have already been split into separate declarations, so there * is no need to handle that here, and "for (var a;;);", which has * been moved out of the loop. * The result of this is that in each case the parent node is replaced * which is generally dangerous in a traversal but is fine here with * the scope creator, as the next node of interest is the parent's * next sibling. */ private void replaceVarWithAssignment(Node n, Node parent, Node gramps) { if (n.hasChildren()) { // The * is being initialize, preserve the new value. parent.removeChild(n); // Convert "var name = value" to "name = value" Node value = n.getFirstChild(); n.removeChild(value); Node replacement = new Node(Token.ASSIGN, n, value); replacement.copyInformationFrom(parent); gramps.replaceChild(parent, NodeUtil.newExpr(replacement)); } else { // It is an empty reference remove it. if (NodeUtil.isStatementBlock(gramps)) { gramps.removeChild(parent); } else if (gramps.getType() == Token.FOR) { // This is the "for (var a in

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> b)..." case. We don't need to worry // about initializers in "for (var a;;)..." as those are moved out // as part of the other normalizations. parent.removeChild(n); gramps.replaceChild(parent, n); } else { Preconditions.checkState(gramps.getType() == Token.LABEL); // We should never get here. LABELs with a single VAR statement should // already have been normalized to have a BLOCK. throw new IllegalStateException("Unexpected LABEL"); } } reportCodeChange("Duplicate VAR declaration"); } } /** * A simple class that causes scope to be created. */ private final class ScopeTicklingCallback implements NodeTraversal.ScopedCallback { @Override public void enterScope(NodeTraversal t) { // Cause the scope to be created, which will cause duplicate // to be found. t.getScope(); } @Override public void exitScope(NodeTraversal t) { // Nothing to do. } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { // Nothing to do. } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return expr; } else { return new JSTypeExpression( new Node(Token.EQUALS, expr.root), expr.sourceName); } } /** * @return Whether this expression denotes an optional {@code @param}. */ public boolean isOptionalArg() { return root.getType() == Token.EQUALS; } /** * @return Whether this expression denotes a rest args {@code @param}. */ public boolean isVarArgs() { return root.getType() == Token.ELLIPSIS; } /** * Evaluates the type expression into a {@code JSType} object. */ public JSType evaluate(StaticScope<JSType> scope, JSTypeRegistry registry) { return registry.createFromTypeNodes(root, sourceName, scope, root.getBooleanProp(Node.BRACELESS_TYPE)); } @Override public boolean equals(Object other) { return other instanceof JSTypeExpression && ((JSTypeExpression) other).root.checkTreeEqualsSilent(root); } @Override public int hashCode() { return root.toStringTree().hashCode(); } Node getRoot() { return root; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.length); assertEquals(expectedSymbolTableError, stErrors[0].getType()); } else { assertEquals("Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors), 0, stErrors.length); } if (warning == null) { assertEquals( "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings), 0, aggregateWarningCount); } else { assertEquals("There should be one warning, repeated " + numRepetitions + " time(s).", numRepetitions, aggregateWarningCount); for (int i = 0; i < numRepetitions; ++i) { JSError[] warnings = errorManagers[i].getWarnings(); JSError actual = warnings[0]; assertEquals(warning, actual.getType()); // Make sure that source information is always provided. if (!allowSourcelessWarnings) { assertTrue("Missing source file name in warning", actual.sourceName != null && !actual.sourceName.isEmpty()); assertTrue("Missing line number in warning", -1 != actual.lineNumber); assertTrue("Missing char number in warning", -1 != actual.getCharno()); } if (description != null) { assertEquals(description, actual.description); } } } if (normalizeEnabled) { normalizeActualCode(compiler, externsRootClone, mainRootClone); } if (mainRootClone.checkTreeEqualsSilent(mainRoot)) { assertFalse( "compiler.reportCodeChange() was called " + "even though nothing changed", hasCodeChanged); } else { assertTrue("compiler.reportCodeChange() should have been called", hasCodeChanged); } if (compareAsTree) { String explanation = expectedRoot.checkTreeEquals(mainRoot); assertNull("\nExpected: " + compiler.toSource(expectedRoot) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } else if (expected != null) { assertEquals( Joiner.on("").join(expected), compiler.toSource(mainRoot)); } // Verify normalization is not invalidated. Node normalizeCheckRootClone = root.cloneTree(); Node normalizeCheckExternsRootClone = root.getFirstChild(); Node normalizeCheckMainRootClone = root.getLastChild(); new PrepareAst(compiler).process( normalizeCheckExternsRootClone, normalizeCheckMainRootClone); String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull("Node structure normalization invalidated.\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); // TODO(johnlenz): enable this for most test cases. // Currently, this invalidates test for while-loops, for-loop // initializers, and other naming.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> However, a set of code // (FoldConstants, etc) runs before the Normalize pass, so this can't be // force on everywhere. if (normalizeEnabled) { new Normalize(compiler, true).process( normalizeCheckExternsRootClone, normalizeCheckMainRootClone); explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull("Normalization invalidated.\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } } else { String errors = ""; for (JSError actualError : compiler.getErrors()) { errors += actualError.description + "\n"; } assertEquals("There should be one error. " + errors, 1, compiler.getErrorCount()); assertEquals(errors, error, compiler.getErrors()[0].getType()); if (warning != null) { String warnings = ""; for (JSError actualError : compiler.getWarnings()) { warnings += actualError.description + "\n"; } assertEquals("There should be one warning. " + warnings, 1, compiler.getWarningCount()); assertEquals(warnings, warning, compiler.getWarnings()[0].getType()); } } } private void normalizeActualCode( Compiler compiler, Node externsRoot, Node mainRoot) { Normalize normalize = new Normalize(compiler, false); normalize.process(externsRoot, mainRoot); } /** * Parses expected js inputs and returns the root of the parse tree. */ protected Node parseExpectedJs(String[] expected) { Compiler compiler = createCompiler(); JSSourceFile[] inputs = new JSSourceFile[expected.length]; for (int i = 0; i < expected.length; i++) { inputs[i] = JSSourceFile.fromCode("expected" + i, expected[i]); } compiler.init(externsInputs, inputs, getOptions()); Node root = compiler.parseInputs(); assertTrue("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root != null); Node externsRoot = root.getFirstChild(); Node mainRoot = externsRoot.getNext(); // Only run the normalize pass, if asked. if (normalizeEnabled && normalizeExpected && !compiler.hasErrors()) { Normalize normalize = new Normalize(compiler, false); normalize.process(externsRoot, mainRoot); } return mainRoot; } protected Node parseExpectedJs(String expected) { return parseExpectedJs(new String[] {expected}); } /** * Generates a list of modules from a list of inputs, such that each module * depends on the module before it. */ static JSModule[] createModuleChain(String... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>i].addDependency(modules[i - 1]); } return modules; } /** * Generates a list of modules from a list of inputs, such that each module * depends on the first module. */ static JSModule[] createModuleStar(String... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[0]); } return modules; } /** * Generates a list of modules from a list of inputs, such that modules * form a bush formation. In a bush formation, module 2 depends * on module 1, and all other modules depend on module 2. */ static JSModule[] createModuleBush(String ... inputs) { Preconditions.checkState(inputs.length > 2); JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[i == 1 ? 0 : 1]); } return modules; } /** * Generates a list of modules from a list of inputs, such that modules * form a tree formation. In a tree formation, module N depends on * module `floor(N/2)`, So the modules form a balanced binary tree. */ static JSModule[] createModuleTree(String ... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[(i - 1) / 2]); } return modules; } /** * Generates a list of modules from a list of inputs. Does not generate any * dependencies between the modules. */ static JSModule[] createModules(String... inputs) { JSModule[] modules = new JSModule[inputs.length]; for (int i = 0; i < inputs.length; i++) { JSModule module = modules[i] = new JSModule("m" + i); module.add(JSSourceFile.fromCode("i" + i, inputs[i])); } return modules; } private static class BlackHoleErrorManager extends BasicErrorManager { private BlackHoleErrorManager(Compiler compiler) { compiler.setErrorManager(this); } @Override public void println(CheckLevel level, JSError error) {} @Override public void printSummary() {} } Compiler createCompiler() { Compiler compiler = new Compiler(); return compiler; } protected void setExpectedSymbolTableError(DiagnosticType type) { this.expectedSymbolTableError = type; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.List; /** * Peephole optimization to fold constants (e.g. x + 1 + 7 --> x + 8). * */ public class PeepholeFoldConstants extends AbstractPeepholeOptimization { static final DiagnosticType DIVIDE_BY_0_ERROR = DiagnosticType.error( "JSC_DIVIDE_BY_0_ERROR", "Divide by 0"); static final DiagnosticType INVALID_GETELEM_INDEX_ERROR = DiagnosticType.error( "JSC_INVALID_GETELEM_INDEX_ERROR", "Array index not integer: {0}"); static final DiagnosticType INDEX_OUT_OF_BOUNDS_ERROR = DiagnosticType.error( "JSC_INDEX_OUT_OF_BOUNDS_ERROR", "Array index out of bounds: {0}"); static final DiagnosticType NEGATING_A_NON_NUMBER_ERROR = DiagnosticType.error( "JSC_NEGATING_A_NON_NUMBER_ERROR", "Can't negate non-numeric value: {0}"); static final DiagnosticType BITWISE_OPERAND_OUT_OF_RANGE = DiagnosticType.error( "JSC_BITWISE_OPERAND_OUT_OF_RANGE", "Operand out of range, bitwise operation will lose information: {0}"); static final DiagnosticType SHIFT_AMOUNT_OUT_OF_BOUNDS = DiagnosticType.error( "JSC_SHIFT_AMOUNT_OUT_OF_BOUNDS", "Shift amount out of bounds: {0}"); static final DiagnosticType FRACTIONAL_BITWISE_OPERAND = DiagnosticType.error( "JSC_FRACTIONAL_BITWISE_OPERAND", "Fractional bitwise operand: {0}"); private static final double MAX_FOLD_NUMBER = Math.pow(2, 53); @Override Node optimizeSubtree(Node subtree) { switch(subtree.getType()) { case

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Token.CALL: return tryFoldKnownMethods(subtree); case Token.NEW: return tryFoldCtorCall(subtree); case Token.TYPEOF: return tryFoldTypeof(subtree); case Token.NOT: case Token.NEG: case Token.BITNOT: return tryFoldUnaryOperator(subtree); default: return tryFoldBinaryOperator(subtree); } } private Node tryFoldBinaryOperator(Node subtree) { Node left = subtree.getFirstChild(); if (left == null) { return subtree; } Node right = left.getNext(); if (right == null) { return subtree; } // If we've reached here, node is truly a binary operator. switch(subtree.getType()) { case Token.GETPROP: return tryFoldGetProp(subtree, left, right); case Token.GETELEM: return tryFoldGetElem(subtree, left, right); case Token.INSTANCEOF: return tryFoldInstanceof(subtree, left, right); case Token.AND: case Token.OR: return tryFoldAndOr(subtree, left, right); case Token.LSH: case Token.RSH: case Token.URSH: return tryFoldShift(subtree, left, right); case Token.ASSIGN: return tryFoldAssign(subtree, left, right); case Token.ADD: return tryFoldAdd(subtree, left, right); case Token.SUB: case Token.DIV: case Token.MOD: return tryFoldArithmeticOp(subtree, left, right); case Token.MUL: case Token.BITAND: case Token.BITOR: Node result = tryFoldArithmeticOp(subtree, left, right); if (result != subtree) { return result; } return tryFoldLeftChildOp(subtree, left, right); case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: return tryFoldComparison(subtree, left, right); default: return subtree; } } /** * Folds 'typeof(foo)' if foo is a literal, e.g. * typeof("bar") --> "string" * typeof(6) --> "number" */ private Node tryFoldTypeof(Node originalTypeofNode) { Preconditions.checkArgument(originalTypeofNode.getType() == Token.TYPEOF); Node argumentNode = originalTypeofNode.getFirstChild(); if (argumentNode == null || !NodeUtil.isLiteralValue(argumentNode, true)) { return originalTypeofNode; } String typeNameString = null; switch (argumentNode.getType()) { case Token.FUNCTION: typeNameString = "function"; break; case Token.STRING: typeNameString =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> "string"; break; case Token.NUMBER: typeNameString = "number"; break; case Token.TRUE: case Token.FALSE: typeNameString = "boolean"; break; case Token.NULL: case Token.OBJECTLIT: case Token.ARRAYLIT: typeNameString = "object"; break; case Token.VOID: typeNameString = "undefined"; break; case Token.NAME: // We assume here that programs don't change the value of the // keyword undefined to something other than the value undefined. if ("undefined".equals(argumentNode.getString())) { typeNameString = "undefined"; } break; } if (typeNameString != null) { Node newNode = Node.newString(typeNameString); originalTypeofNode.getParent().replaceChild(originalTypeofNode, newNode); reportCodeChange(); return newNode; } return originalTypeofNode; } private Node tryFoldUnaryOperator(Node n) { Preconditions.checkState(n.hasOneChild()); Node left = n.getFirstChild(); Node parent = n.getParent(); if (left == null) { return n; } TernaryValue leftVal = NodeUtil.getBooleanValue(left); if (leftVal == TernaryValue.UNKNOWN) { return n; } switch (n.getType()) { case Token.NOT: int result = leftVal.toBoolean(true) ? Token.FALSE : Token.TRUE; Node replacementNode = new Node(result); parent.replaceChild(n, replacementNode); reportCodeChange(); return replacementNode; case Token.NEG: try { if (left.getType() == Token.NAME) { if (left.getString().equals("Infinity")) { // "-Infinity" is valid and a literal, don't modify it. return n; } else if (left.getString().equals("NaN")) { // "-NaN" is "NaN". n.removeChild(left); parent.replaceChild(n, left); reportCodeChange(); return left; } } double negNum = -left.getDouble(); Node negNumNode = Node.newNumber(negNum); parent.replaceChild(n, negNumNode); reportCodeChange(); return negNumNode; } catch (UnsupportedOperationException ex) { // left is not a number node, so do not replace, but warn the // user because they can't be doing anything good error(NEGATING_A_NON_NUMBER_ERROR, left); return n; } case Token.BITNOT: try { double val = left.getDouble(); if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) { int intVal = (int) val; if (intVal == val) { Node notIntValNode = Node.newNumber(~intVal); parent.replaceChild(n, notIntValNode); reportCodeChange(); return not

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>IntValNode; } else { error(FRACTIONAL_BITWISE_OPERAND, left); return n; } } else { error(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } } catch (UnsupportedOperationException ex) { // left is not a number node, so do not replace, but warn the // user because they can't be doing anything good error(NEGATING_A_NON_NUMBER_ERROR, left); return n; } default: return n; } } /** * Try to fold {@code left instanceof right} into {@code true} * or {@code false}. */ private Node tryFoldInstanceof(Node n, Node left, Node right) { Preconditions.checkArgument(n.getType() == Token.INSTANCEOF); // TODO(johnlenz) Use type information if available to fold // instanceof. if (NodeUtil.isLiteralValue(left, true) && !mayHaveSideEffects(right)) { Node replacementNode = null; if (NodeUtil.isImmutableValue(left)) { // Non-object types are never instances. replacementNode = new Node(Token.FALSE); } else if (right.getType() == Token.NAME && "Object".equals(right.getString())) { replacementNode = new Node(Token.TRUE); } if (replacementNode != null) { n.getParent().replaceChild(n, replacementNode); reportCodeChange(); return replacementNode; } } return n; } private Node tryFoldAssign(Node n, Node left, Node right) { Preconditions.checkArgument(n.getType() == Token.ASSIGN); // Tries to convert x = x + y -> x += y; if (!right.hasChildren() || right.getFirstChild().getNext() != right.getLastChild()) { // RHS must have two children. return n; } if (mayHaveSideEffects(left)) { return n; } Node leftChild = right.getFirstChild(); if (!areNodesEqualForInlining(left, leftChild)) { return n; } int newType = -1; switch (right.getType()) { case Token.ADD: newType = Token.ASSIGN_ADD; break; case Token.BITAND: newType = Token.ASSIGN_BITAND; break; case Token.BITOR: newType = Token.ASSIGN_BITOR; break; case Token.BITXOR: newType = Token.ASSIGN_BITXOR; break; case Token.DIV: newType = Token.ASSIGN_DIV; break; case Token.LSH: newType = Token.ASSIGN_LSH; break; case Token.MOD: newType = Token.ASSIGN_MOD; break; case Token.MUL: newType = Token.ASSIGN_MUL; break; case Token.RSH:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> newType = Token.ASSIGN_RSH; break; case Token.SUB: newType = Token.ASSIGN_SUB; break; case Token.URSH: newType = Token.ASSIGN_URSH; break; default: return n; } Node newNode = new Node(newType, left.detachFromParent(), right.getLastChild().detachFromParent()); n.getParent().replaceChild(n, newNode); reportCodeChange(); return newNode; } /** * Try to fold a AND/OR node. */ private Node tryFoldAndOr(Node n, Node left, Node right) { Node parent = n.getParent(); Node result = null; int type = n.getType(); TernaryValue leftVal = NodeUtil.getBooleanValue(left); if (leftVal != TernaryValue.UNKNOWN) { boolean lval = leftVal.toBoolean(true); // (TRUE || x) => TRUE (also, (3 || x) => 3) // (FALSE && x) => FALSE if (lval && type == Token.OR || !lval && type == Token.AND) { result = left; } else { // (FALSE || x) => x // (TRUE && x) => x result = right; } } // Note: Right hand side folding is handled by // PeepholeSubstituteAlternateSyntax#tryMinimizeCondition if (result != null) { // Fold it! n.removeChild(result); parent.replaceChild(n, result); reportCodeChange(); return result; } else { return n; } } /** * Expressions such as [foo() + 'a' + 'b'] generate parse trees * where no node has two const children ((foo() + 'a') + 'b'), so * tryFoldAdd() won't fold it -- tryFoldLeftChildAdd() will (for Strings). * Specifically it folds Add exprssions where: * - The left child is also and add expression * - The right child is a constant value * - The left child's right child is a STRING constant. */ private Node tryFoldLeftChildAdd(Node n, Node left, Node right) { if (NodeUtil.isLiteralValue(right, false) && left.getType() == Token.ADD && left.getChildCount() == 2) { Node ll = left.getFirstChild(); Node lr = ll.getNext(); // Left's right child MUST be a string. We would not want to fold // foo() + 2 + 'a' because we don't know what foo() will return, and // therefore we don't know if left is a string concat, or a numeric add. if (lr.getType() != Token.STRING) { return n; } String leftString = NodeUtil.getStringValue(lr); String rightString = NodeUtil.getStringValue(right

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>); if (leftString != null && rightString != null) { left.removeChild(ll); String result = leftString + rightString; n.replaceChild(left, ll); n.replaceChild(right, Node.newString(result)); reportCodeChange(); } } return n; } /** * Try to fold an ADD node with constant operands */ private Node tryFoldAddConstant(Node n, Node left, Node right) { if (left.getType() == Token.STRING || right.getType() == Token.STRING) { // Add strings. String leftString = NodeUtil.getStringValue(left); String rightString = NodeUtil.getStringValue(right); if (leftString != null && rightString != null) { Node newStringNode = Node.newString(leftString + rightString); n.getParent().replaceChild(n, newStringNode); reportCodeChange(); return newStringNode; } } else { // Try arithmetic add return tryFoldArithmeticOp(n, left, right); } return n; } /** * Try to fold arithmetic binary operators */ private Node tryFoldArithmeticOp(Node n, Node left, Node right) { Node result = performArithmeticOp(n.getType(), left, right); if (result != null) { n.getParent().replaceChild(n, result); reportCodeChange(); return result; } return n; } /** * Try to fold arithmetic binary operators */ private Node performArithmeticOp(int opType, Node left, Node right) { // Unlike other operations, ADD operands are not always converted // to Number. if (opType == Token.ADD && (left.getType() != Token.NUMBER || right.getType() != Token.NUMBER)) { return null; } double result; Double lValObj = NodeUtil.getNumberValue(left); if (lValObj == null) { return null; } Double rValObj = NodeUtil.getNumberValue(right); if (rValObj == null) { return null; } double lval = lValObj; double rval = rValObj; switch (opType) { case Token.BITAND: if (!areValidInts(lval, rval)) { return null; } result = (int)lval & (int)rval; break; case Token.BITOR: if (!areValidInts(lval, rval)) { return null; } result = (int)lval | (int)rval; break; case Token.ADD: result = lval + rval; break; case Token.SUB: result = lval - rval; break; case Token.MUL: result = lval * rval; break; case Token.MOD: if (rval == 0) { error(DIVIDE_BY_0

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>_ERROR, right); return null; } result = lval % rval; break; case Token.DIV: if (rval == 0) { error(DIVIDE_BY_0_ERROR, right); return null; } result = lval / rval; break; default: throw new Error("Unexpected arithmetic operator"); } // TODO(johnlenz): consider removing the result length check. // length of the left and right value plus 1 byte for the operator. if (String.valueOf(result).length() <= String.valueOf(lval).length() + String.valueOf(rval).length() + 1 && // Do not try to fold arithmetic for numbers > 2^53. After that // point, fixed-point math starts to break down and become inaccurate. Math.abs(result) <= MAX_FOLD_NUMBER) { Node newNumber = Node.newNumber(result); return newNumber; } else if (Double.isNaN(result)) { return Node.newString(Token.NAME, "NaN"); } else if (result == Double.POSITIVE_INFINITY) { return Node.newString(Token.NAME, "Infinity"); } else if (result == Double.NEGATIVE_INFINITY) { return new Node(Token.NEG, Node.newString(Token.NAME, "Infinity")); } return null; } /** * @return Whether the double can be precisely represented as a int. */ private boolean isValidInt(double val) { return !(val < Integer.MIN_VALUE || val > Integer.MAX_VALUE) && val == (int)val; } /** * @return Whether the parameters are doubles can be precisely represented * as a int. */ private boolean areValidInts(double val1, double val2) { return isValidInt(val1) && isValidInt(val2); } /** * Expressions such as [foo() * 10 * 20] generate parse trees * where no node has two const children ((foo() * 10) * 20), so * performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will. * Specifically it folds associative expressions where: * - The left child is also an associative expression of the same time. * - The right child is a constant NUMBER constant. * - The left child's right child is a NUMBER constant. */ private Node tryFoldLeftChildOp(Node n, Node left, Node right) { int opType = n.getType(); // Note: ADD is not associative when used as a string concat operator. Preconditions.checkState( NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)); // TODO(johnlenz): create and use a getNumberValue. if (right.getType() == Token.NUMBER && left.getType() == op

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Type) { Preconditions.checkState(left.getChildCount() == 2); Node ll = left.getFirstChild(); Node lr = ll.getNext(); Node valueToCombine; if (ll.getType() == Token.NUMBER) { valueToCombine = ll; } else if (lr.getType() == Token.NUMBER) { valueToCombine = lr; } else { // Nothing to do. return n; } Node replacement = performArithmeticOp(opType, valueToCombine, right); if (replacement != null) { left.removeChild(valueToCombine); n.replaceChild(left, left.removeFirstChild()); n.replaceChild(right, replacement); reportCodeChange(); } } return n; } private Node tryFoldAdd(Node node, Node left, Node right) { Preconditions.checkArgument(node.getType() == Token.ADD); if (NodeUtil.isLiteralValue(left, false) && NodeUtil.isLiteralValue(right, false)) { // 6 + 7 return tryFoldAddConstant(node, left, right); } else { // a + 7 or 6 + a return tryFoldLeftChildAdd(node, left, right); } } /** * Try to fold shift operations */ private Node tryFoldShift(Node n, Node left, Node right) { if (left.getType() == Token.NUMBER && right.getType() == Token.NUMBER) { double result; double lval = left.getDouble(); double rval = right.getDouble(); // check ranges. We do not do anything that would clip the double to // a 32-bit range, since the user likely does not intend that. if (!(lval >= Integer.MIN_VALUE && lval <= Integer.MAX_VALUE)) { error(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } // only the lower 5 bits are used when shifting, so don't do anything // if the shift amount is outside [0,32) if (!(rval >= 0 && rval < 32)) { error(SHIFT_AMOUNT_OUT_OF_BOUNDS, right); return n; } // Convert the numbers to ints int lvalInt = (int) lval; if (lvalInt != lval) { error(FRACTIONAL_BITWISE_OPERAND, left); return n; } int rvalInt = (int) rval; if (rvalInt != rval) { error(FRACTIONAL_BITWISE_OPERAND, right); return n; } switch (n.getType()) { case Token.LSH: result = lvalInt << rvalInt; break; case Token.RSH: result = lvalInt >> rvalInt; break; case Token.URSH: // JavaScript handles zero shifts on signed numbers differently than

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // Java as an Java int can not represent the unsigned 32-bit number // where JavaScript can so use a long here. long lvalLong = lvalInt & 0xffffffffL; result = lvalLong >>> rvalInt; break; default: throw new AssertionError("Unknown shift operator: " + Node.tokenToName(n.getType())); } Node newNumber = Node.newNumber(result); n.getParent().replaceChild(n, newNumber); reportCodeChange(); return newNumber; } return n; } /** * Try to fold comparison nodes, e.g == */ @SuppressWarnings("fallthrough") private Node tryFoldComparison(Node n, Node left, Node right) { if (!NodeUtil.isLiteralValue(left, false) || !NodeUtil.isLiteralValue(right, false)) { // We only handle non-literal operands for LT and GT. if (n.getType() != Token.GT && n.getType() != Token.LT) { return n; } } int op = n.getType(); boolean result; // TODO(johnlenz): Use the JSType to compare nodes of different types. boolean rightLiteral = NodeUtil.isLiteralValue(right, false); boolean undefinedRight = ((Token.NAME == right.getType() && right.getString().equals("undefined")) || (Token.VOID == right.getType() && NodeUtil.isLiteralValue(right.getFirstChild(), false))); switch (left.getType()) { case Token.VOID: if (!NodeUtil.isLiteralValue(left.getFirstChild(), false)) { return n; } else if (!rightLiteral) { return n; } else { result = compareToUndefined(right, op); } break; case Token.NULL: case Token.TRUE: case Token.FALSE: if (undefinedRight) { result = compareToUndefined(left, op); break; } int rhType = right.getType(); if (rhType != Token.TRUE && rhType != Token.FALSE && rhType != Token.NULL) { return n; } switch (op) { case Token.SHEQ: case Token.EQ: result = left.getType() == right.getType(); break; case Token.SHNE: case Token.NE: result = left.getType() != right.getType(); break; case Token.GE: case Token.LE: case Token.GT: case Token.LT: Boolean compareResult = compareAsNumbers(op, left, right); if (compareResult != null) { result = compareResult; } else { return n; } break; default: return n; // we only handle == and != here } break; case Token.THIS: if (right.getType() != Token.THIS) { return n; } switch (op)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { case Token.SHEQ: case Token.EQ: result = true; break; case Token.SHNE: case Token.NE: result = false; break; // We can only handle == and != here. // GT, LT, GE, LE depend on the type of "this" and how it will // be converted to number. The results are different depending on // whether it is a string, NaN or other number value. default: return n; } break; case Token.STRING: if (undefinedRight) { result = compareToUndefined(left, op); break; } if (Token.STRING != right.getType()) { return n; // Only eval if they are the same type } switch (op) { case Token.SHEQ: case Token.EQ: result = left.getString().equals(right.getString()); break; case Token.SHNE: case Token.NE: result = !left.getString().equals(right.getString()); break; default: return n; // we only handle == and != here } break; case Token.NUMBER: if (undefinedRight) { result = compareToUndefined(left, op); break; } if (Token.NUMBER != right.getType()) { return n; // Only eval if they are the same type } Boolean compareResult = compareAsNumbers(op, left, right); if (compareResult != null) { result = compareResult; } else { return null; } break; case Token.NAME: if (undefinedRight) { result = compareToUndefined(left, op); break; } if (rightLiteral) { boolean undefinedLeft = (left.getString().equals("undefined")); if (undefinedLeft) { result = compareToUndefined(right, op); break; } } if (Token.NAME != right.getType()) { return n; // Only eval if they are the same type } String ln = left.getString(); String rn = right.getString(); if (!ln.equals(rn)) { return n; // Not the same value name. } switch (op) { // If we knew the named value wouldn't be NaN, it would be nice // to handle EQ,NE,LE,GE,SHEQ, and SHNE. case Token.LT: case Token.GT: result = false; break; default: return n; // don't handle that op } break; default: // assert, this should cover all consts return n; } Node newNode = new Node(result ? Token.TRUE : Token.FALSE); n.getParent().replaceChild(n, newNode); reportCodeChange(); return newNode; } /** * The result of the comparison as a Boolean or null if the * result could not be determined. */

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private Boolean compareAsNumbers(int op, Node left, Node right) { Double leftValue = NodeUtil.getNumberValue(left); if (leftValue == null) { return null; } Double rightValue = NodeUtil.getNumberValue(right); if (rightValue == null) { return null; } double lv = leftValue; double rv = rightValue; Boolean result; switch (op) { case Token.SHEQ: case Token.EQ: Preconditions.checkState( left.getType() == Token.NUMBER && right.getType() == Token.NUMBER); result = lv == rv; break; case Token.SHNE: case Token.NE: Preconditions.checkState( left.getType() == Token.NUMBER && right.getType() == Token.NUMBER); result = lv != rv; break; case Token.LE: result = lv <= rv; break; case Token.LT: result = lv < rv; break; case Token.GE: result = lv >= rv; break; case Token.GT: result = lv > rv; break; default: return null; // don't handle that op } return result; } /** * @param value The value to compare to "undefined" * @param op The boolean op to compare with * @return Whether the boolean op is true or false */ private boolean compareToUndefined(Node value, int op) { boolean valueUndefined = ((Token.NAME == value.getType() && value.getString().equals("undefined")) || (Token.VOID == value.getType() && NodeUtil.isLiteralValue(value.getFirstChild(), false))); boolean valueNull = (Token.NULL == value.getType()); boolean equivalent = valueUndefined || valueNull; switch (op) { case Token.EQ: // undefined is only equal to null or an undefined value return equivalent; case Token.NE: return !equivalent; case Token.SHEQ: return valueUndefined; case Token.SHNE: return !valueUndefined; case Token.LT: case Token.GT: case Token.LE: case Token.GE: return false; default: throw new IllegalStateException("unexpected."); } } /** * Try to fold away unnecessary object instantiation. * e.g. this[new String('eval')] -> this.eval */ private Node tryFoldCtorCall(Node n) { Preconditions.checkArgument(n.getType() == Token.NEW); // we can remove this for GETELEM calls (anywhere else?) if (inForcedStringContext(n)) { return tryFoldInForcedStringContext(n); } return n; } /** Returns whether this node must be coerced to a string. */ private boolean inForcedStringContext(Node n) { return n.getParent().getType() == Token.GETELEM && n.getParent().getLastChild() == n; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private Node tryFoldInForcedStringContext(Node n) { // For now, we only know how to fold ctors. Preconditions.checkArgument(n.getType() == Token.NEW); Node objectType = n.getFirstChild(); if (objectType.getType() != Token.NAME) { return n; } if (objectType.getString().equals("String")) { Node value = objectType.getNext(); String stringValue = null; if (value == null) { stringValue = ""; } else { if (!NodeUtil.isImmutableValue(value)) { return n; } stringValue = NodeUtil.getStringValue(value); } if (stringValue == null) { return n; } Node parent = n.getParent(); Node newString = Node.newString(stringValue); parent.replaceChild(n, newString); newString.copyInformationFrom(parent); reportCodeChange(); return newString; } return n; } private Node tryFoldKnownMethods(Node subtree) { // For now we only support .join(), // .indexOf(), .substring() and .substr() subtree = tryFoldArrayJoin(subtree); if (subtree.getType() == Token.CALL) { subtree = tryFoldKnownStringMethods(subtree); } return subtree; } /** * Try to eveluate known String methods * .indexOf(), .substr(), .substring() */ private Node tryFoldKnownStringMethods(Node subtree) { Preconditions.checkArgument(subtree.getType() == Token.CALL); // check if this is a call on a string method // then dispatch to specific folding method. Node callTarget = subtree.getFirstChild(); if (callTarget == null) { return subtree; } Node firstArg = callTarget.getNext(); if (firstArg == null) { return subtree; } if (!NodeUtil.isGet(callTarget) || !NodeUtil.isImmutableValue(firstArg)) { return subtree; } Node stringNode = callTarget.getFirstChild(); Node functionName = stringNode.getNext(); if ((stringNode.getType() != Token.STRING) || ( (functionName.getType() != Token.STRING))) { return subtree; } String functionNameString = functionName.getString(); if (functionNameString.equals("indexOf") || functionNameString.equals("lastIndexOf")) { subtree = tryFoldStringIndexOf(subtree, functionNameString, stringNode, firstArg); } else if (functionNameString.equals("substr")) { subtree = tryFoldStringSubstr(subtree, stringNode, firstArg); } else if (functionNameString.equals("substring")) { subtree = tryFoldStringSubstring(subtree, stringNode, firstArg); } return subtree; } /** * Try to evaluate String.indexOf/lastIndexOf: * "abcdef".indexOf("bc") ->

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> 1 * "abcdefbc".indexOf("bc", 3) -> 6 */ private Node tryFoldStringIndexOf( Node n, String functionName, Node lstringNode, Node firstArg) { Preconditions.checkArgument(n.getType() == Token.CALL); Preconditions.checkArgument(lstringNode.getType() == Token.STRING); String lstring = NodeUtil.getStringValue(lstringNode); boolean isIndexOf = functionName.equals("indexOf"); Node secondArg = firstArg.getNext(); String searchValue = NodeUtil.getStringValue(firstArg); // searchValue must be a valid string. if (searchValue == null) { return n; } int fromIndex = isIndexOf ? 0 : lstring.length(); if (secondArg != null) { // Third-argument and non-numeric second arg are problematic. Discard. if ((secondArg.getNext() != null) || (secondArg.getType() != Token.NUMBER)) { return n; } else { fromIndex = (int) secondArg.getDouble(); } } int indexVal = isIndexOf ? lstring.indexOf(searchValue, fromIndex) : lstring.lastIndexOf(searchValue, fromIndex); Node newNode = Node.newNumber(indexVal); n.getParent().replaceChild(n, newNode); reportCodeChange(); return newNode; } /** * Try to fold an array join: ['a', 'b', 'c'].join('') -> 'abc'; */ private Node tryFoldArrayJoin(Node n) { Node callTarget = n.getFirstChild(); if (callTarget == null) { return n; } Node right = callTarget.getNext(); if (right == null) { return n; } if (!NodeUtil.isGetProp(callTarget) || !NodeUtil.isImmutableValue(right)) { return n; } Node arrayNode = callTarget.getFirstChild(); Node functionName = arrayNode.getNext(); if ((arrayNode.getType() != Token.ARRAYLIT) || !functionName.getString().equals("join")) { return n; } String joinString = NodeUtil.getStringValue(right); List<Node> arrayFoldedChildren = Lists.newLinkedList(); StringBuilder sb = null; int foldedSize = 0; Node prev = null; Node elem = arrayNode.getFirstChild(); // Merges adjacent String nodes. while (elem != null) { if (NodeUtil.isImmutableValue(elem)) { if (sb == null) { sb = new StringBuilder(); } else { sb.append(joinString); } sb.append(NodeUtil.getStringValue(elem)); } else { if (sb != null) { Preconditions.checkNotNull(prev); // + 2 for the quotes. foldedSize += sb.length() + 2; arrayFoldedChildren.add( Node.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>newString(sb.toString()).copyInformationFrom(prev)); sb = null; } foldedSize += InlineCostEstimator.getCost(elem); arrayFoldedChildren.add(elem); } prev = elem; elem = elem.getNext(); } if (sb != null) { Preconditions.checkNotNull(prev); // + 2 for the quotes. foldedSize += sb.length() + 2; arrayFoldedChildren.add( Node.newString(sb.toString()).copyInformationFrom(prev)); } // one for each comma. foldedSize += arrayFoldedChildren.size() - 1; int originalSize = InlineCostEstimator.getCost(n); switch (arrayFoldedChildren.size()) { case 0: Node emptyStringNode = Node.newString(""); n.getParent().replaceChild(n, emptyStringNode); reportCodeChange(); return emptyStringNode; case 1: Node foldedStringNode = arrayFoldedChildren.remove(0); if (foldedSize > originalSize) { return n; } arrayNode.detachChildren(); if (foldedStringNode.getType() != Token.STRING) { // If the Node is not a string literal, ensure that // it is coerced to a string. Node replacement = new Node(Token.ADD, Node.newString("").copyInformationFrom(right), foldedStringNode); foldedStringNode = replacement; } n.getParent().replaceChild(n, foldedStringNode); reportCodeChange(); return foldedStringNode; default: // No folding could actually be performed. if (arrayFoldedChildren.size() == arrayNode.getChildCount()) { return n; } int kJoinOverhead = "[].join()".length(); foldedSize += kJoinOverhead; foldedSize += InlineCostEstimator.getCost(right); if (foldedSize > originalSize) { return n; } arrayNode.detachChildren(); for (Node node : arrayFoldedChildren) { arrayNode.addChildToBack(node); } reportCodeChange(); break; } return n; } /** * Try to fold .substr() calls on strings */ private Node tryFoldStringSubstr(Node n, Node stringNode, Node arg1) { Preconditions.checkArgument(n.getType() == Token.CALL); Preconditions.checkArgument(stringNode.getType() == Token.STRING); int start, length; String stringAsString = stringNode.getString(); // TODO(nicksantos): We really need a NodeUtil.getNumberValue // function. if (arg1 != null && arg1.getType() == Token.NUMBER) { start = (int) arg1.getDouble(); } else { return n; } Node arg2 = arg1.getNext(); if (arg

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>2 != null) { if (arg2.getType() == Token.NUMBER) { length = (int) arg2.getDouble(); } else { return n; } if (arg2.getNext() != null) { // If we got more args than we expected, bail out. return n; } } else { // parameter 2 not passed length = stringAsString.length() - start; } // Don't handle these cases. The specification actually does // specify the behavior in some of these cases, but we haven't // done a thorough investigation that it is correctly implemented // in all browsers. if ((start + length) > stringAsString.length() || (length < 0) || (start < 0)) { return n; } String result = stringAsString.substring(start, start + length); Node resultNode = Node.newString(result); Node parent = n.getParent(); parent.replaceChild(n, resultNode); reportCodeChange(); return resultNode; } /** * Try to fold .substring() calls on strings */ private Node tryFoldStringSubstring(Node n, Node stringNode, Node arg1) { Preconditions.checkArgument(n.getType() == Token.CALL); Preconditions.checkArgument(stringNode.getType() == Token.STRING); int start, end; String stringAsString = stringNode.getString(); if (arg1 != null && arg1.getType() == Token.NUMBER) { start = (int) arg1.getDouble(); } else { return n; } Node arg2 = arg1.getNext(); if (arg2 != null) { if (arg2.getType() == Token.NUMBER) { end = (int) arg2.getDouble(); } else { return n; } if (arg2.getNext() != null) { // If we got more args than we expected, bail out. return n; } } else { // parameter 2 not passed end = stringAsString.length(); } // Don't handle these cases. The specification actually does // specify the behavior in some of these cases, but we haven't // done a thorough investigation that it is correctly implemented // in all browsers. if ((end > stringAsString.length()) || (start > stringAsString.length()) || (end < 0) || (start < 0)) { return n; } String result = stringAsString.substring(start, end); Node resultNode = Node.newString(result); Node parent = n.getParent(); parent.replaceChild(n, resultNode); reportCodeChange(); return resultNode; } /** * Try to fold array-element. e.g [1, 2, 3][10]; */ private Node tryFoldGetElem(Node n, Node left, Node right) { Preconditions.checkArgument(n.getType() == Token.GETELEM);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (left.getType() == Token.ARRAYLIT) { if (right.getType() != Token.NUMBER) { // Sometimes people like to use complex expressions to index into // arrays, or strings to index into array methods. return n; } double index = right.getDouble(); int intIndex = (int) index; if (intIndex != index) { error(INVALID_GETELEM_INDEX_ERROR, right); return n; } if (intIndex < 0) { error(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } Node elem = left.getFirstChild(); for (int i = 0; elem != null && i < intIndex; i++) { elem = elem.getNext(); } if (elem == null) { error(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } // Replace the entire GETELEM with the value left.removeChild(elem); n.getParent().replaceChild(n, elem); reportCodeChange(); return elem; } return n; } /** * Try to fold array-length. e.g [1, 2, 3].length ==> 3, [x, y].length ==> 2 */ private Node tryFoldGetProp(Node n, Node left, Node right) { Preconditions.checkArgument(n.getType() == Token.GETPROP); if (right.getType() == Token.STRING && right.getString().equals("length")) { int knownLength = -1; switch (left.getType()) { case Token.ARRAYLIT: if (mayHaveSideEffects(left)) { // Nope, can't fold this, without handling the side-effects. return n; } knownLength = left.getChildCount(); break; case Token.STRING: knownLength = left.getString().length(); break; default: // Not a foldable case, forget it. return n; } Preconditions.checkState(knownLength != -1); Node lengthNode = Node.newNumber(knownLength); n.getParent().replaceChild(n, lengthNode); reportCodeChange(); return lengthNode; } return n; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> the content of A is "upward exposed" * at point N_4 and N_5. * * Example: * * A = 1; * ... * N_3: * N_4: print(A); * N_5: y = A; * N_6: A = 1; * N_7: print(A); * * At N_3, reads of A in {N_4, N_5} are said to be upward exposed. */ static final class ReachingUses implements LatticeElement { final Multimap<Var, Node> mayUseMap; public ReachingUses() { mayUseMap = HashMultimap.create(); } /** * Copy constructor. * * @param other The constructed object is a replicated copy of this element. */ public ReachingUses(ReachingUses other) { mayUseMap = HashMultimap.create(other.mayUseMap); } @Override public boolean equals(Object other) { return (other instanceof ReachingUses) && ((ReachingUses) other).mayUseMap.equals(this.mayUseMap); } @Override public int hashCode() { return mayUseMap.hashCode(); } } /** * The join is a simple union because of the "may be" nature of the analysis. * * Consider: A = 1; if (x) { A = 2 }; alert(A); * * The read of A "may be" exposed to A = 1 in the beginning. */ private static class ReachingUsesJoinOp implements JoinOp<ReachingUses> { @Override public ReachingUses apply(List<ReachingUses> from) { ReachingUses result = new ReachingUses(); for (ReachingUses uses : from) { result.mayUseMap.putAll(uses.mayUseMap); } return result; } } @Override boolean isForward() { return false; } @Override ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); computeMayUse(n, n, output, false); return output; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: case Token.DO: case Token.IF: computeMayUse( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); return;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case Token.FOR: if (!NodeUtil.isForIn(n)) { computeMayUse( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (NodeUtil.isVar(lhs)) { lhs = lhs.getLastChild(); // for(var x in y) {...} } if (NodeUtil.isName(lhs) && !conditional) { removeFromUseIfLocal(lhs.getString(), output); } computeMayUse(rhs, cfgNode, output, conditional); } return; case Token.AND: case Token.OR: computeMayUse(n.getLastChild(), cfgNode, output, true); computeMayUse(n.getFirstChild(), cfgNode, output, conditional); return; case Token.HOOK: computeMayUse(n.getLastChild(), cfgNode, output, true); computeMayUse(n.getFirstChild().getNext(), cfgNode, output, true); computeMayUse(n.getFirstChild(), cfgNode, output, conditional); return; case Token.VAR: Node varName = n.getFirstChild(); Preconditions.checkState(n.hasChildren(), "AST should be normalized"); if (varName.hasChildren()) { computeMayUse(varName.getFirstChild(), cfgNode, output, conditional); if (!conditional) { removeFromUseIfLocal(varName.getString(), output); } } return; default: if (NodeUtil.isAssignmentOp(n) && NodeUtil.isName(n.getFirstChild())) { Node name = n.getFirstChild(); if (!conditional) { removeFromUseIfLocal(name.getString(), output); } // In case of a += "Hello". There is a read of a. if (!NodeUtil.isAssign(n)) { addToUseIfLocal(name.getString(), cfgNode, output); } computeMayUse(name.getNext(), cfgNode, output, conditional); } else { /* * We want to traverse in reverse order because we want the LAST * definition in the sub-tree.... * But we have no better way to traverse in reverse other :'( */ for (Node c = n.getLastChild(); c != null; c = n.getChildBefore(c)) { computeMayUse(c, cfgNode, output, conditional); } } } } /** * Sets the variable for the given name to the node value in the upward * exposed lattice. Do nothing if the variable name is one of the escaped * variable. */ private void addToUseIfLocal(String name, Node node, ReachingUses use) { Var var = jsScope.getVar(name); if (var == null || var.scope != jsScope) { return; } if (!escaped.contains(var)) { use.mayUse

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Map.put(var, node); } } /** * Removes the variable for the given name from the node value in the upward * exposed lattice. Do nothing if the variable name is one of the escaped * variable. */ private void removeFromUseIfLocal(String name, ReachingUses use) { Var var = jsScope.getVar(name); if (var == null || var.scope != jsScope) { return; } if (!escaped.contains(var)) { use.mayUseMap.removeAll(var); } } /** * Gets a list of nodes that may be using the value assigned to {@code name} * in {@code defNode}. {@code defNode} must be one of the control flow graph * nodes. * * @param name name of the variable. It can only be names of local variable * that are not function parameters, escaped variables or variables * declared in catch. * @param defNode The list of upward exposed use for the variable. */ Collection<Node> getUses(String name, Node defNode) { GraphNode<Node, Branch> n = getCfg().getNode(defNode); Preconditions.checkNotNull(n); FlowState<ReachingUses> state = n.getAnnotation(); return state.getOut().mayUseMap.get(jsScope.getVar(name)); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> like * {@code @return} the bus. * and they clearly don't mean that "the" is a type. In these cases, we're * forgiving and try to guess whether or not "the" is a type when it's not * clear. */ private boolean forgiving = false; /** * Create a named type based on the reference. */ UnresolvedTypeExpression(JSTypeRegistry registry, Node typeExpr, String sourceName, boolean forgiving) { super(registry, false); Preconditions.checkNotNull(typeExpr); this.typeExpr = typeExpr; this.sourceName = sourceName; this.forgiving = forgiving; } /** * Resolve the referenced type within the enclosing scope. */ @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) { return registry.createFromTypeNodes(typeExpr, sourceName, enclosing, forgiving); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> input.getModule(); Preconditions.checkNotNull(module); entryPointInputsPerModule.put(module, input); } // Clear the modules of their inputs. This also nulls out // the input's reference to its module. for (JSModule module : getAllModules()) { module.removeAll(); } // Figure out which sources *must* be in each module, or in one // of that module's dependencies. for (JSModule module : entryPointInputsPerModule.keySet()) { List<CompilerInput> transitiveClosure = sorter.getSortedDependenciesOf( entryPointInputsPerModule.get(module)); for (CompilerInput input : transitiveClosure) { JSModule oldModule = input.getModule(); if (oldModule == null) { input.setModule(module); } else { input.setModule(null); input.setModule( getDeepestCommonDependencyInclusive(oldModule, module)); } } } // All the inputs are pointing to the modules that own them. Yeah! // Update the modules to reflect this. for (CompilerInput input : absoluteOrder) { JSModule module = input.getModule(); if (module != null) { module.add(input); } } // Now, generate the sorted result. List<CompilerInput> result = Lists.newArrayList(); for (JSModule module : getAllModulesInDependencyOrder()) { result.addAll(module.getInputs()); } return result; } /** * A module depth comparator that considers a deeper module to be * "greater than" a shallower module. Uses module names to * consistently break ties. */ private class DepthComparator implements Comparator<JSModule> { public int compare(JSModule m1, JSModule m2) { return depthCompare(m1, m2); } } /** * A module depth comparator that considers a deeper module to be "less than" * a shallower module. Uses module names to consistently break ties. */ private class InverseDepthComparator implements Comparator<JSModule> { public int compare(JSModule m1, JSModule m2) { return depthCompare(m2, m1); } } private int depthCompare(JSModule m1, JSModule m2) { if (m1 == m2) { return 0; } int d1 = m1.getDepth(); int d2 = m2.getDepth(); return d1 < d2 ? -1 : d2 == d1 ? m1.getName().compareTo(m2.getName()) : 1; } /* * Exception class for declaring when the modules being fed into a * JSModuleGraph as input aren't in dependence order, and so can't be * processed for caching of various dependency-related queries. */ protected static class ModuleDependenceException extends IllegalArgumentException { private static final long serialVersionUID = 1; private final JSModule module; private

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> of all local * variables and a variable is live if it is in the set. */ static class LiveVariableLattice implements LatticeElement { private final BitSet liveSet; /** * @param numVars Number of all local variables. */ private LiveVariableLattice(int numVars) { this.liveSet = new BitSet(numVars); } private LiveVariableLattice(LiveVariableLattice other) { Preconditions.checkNotNull(other); this.liveSet = (BitSet) other.liveSet.clone(); } @Override public boolean equals(Object other) { Preconditions.checkNotNull(other); return (other instanceof LiveVariableLattice) && this.liveSet.equals(((LiveVariableLattice) other).liveSet); } public boolean isLive(Var v) { Preconditions.checkNotNull(v); return liveSet.get(v.index); } public boolean isLive(int index) { return liveSet.get(index); } @Override public String toString() { return liveSet.toString(); } @Override public int hashCode() { return liveSet.hashCode(); } } // The scope of the function that we are analyzing. private final Scope jsScope; private final Set<Var> escaped; LiveVariablesAnalysis(ControlFlowGraph<Node> cfg, Scope jsScope, AbstractCompiler compiler) { super(cfg, new LiveVariableJoinOp()); this.jsScope = jsScope; this.escaped = Sets.newHashSet(); computeEscaped(jsScope, escaped, compiler); } public Set<Var> getEscapedLocals() { return escaped; } public int getVarIndex(String var) { return jsScope.getVar(var).index; } @Override boolean isForward() { return false; } @Override LiveVariableLattice createEntryLattice() { return new LiveVariableLattice(jsScope.getVarCount()); } @Override LiveVariableLattice createInitialEstimateLattice() { return new LiveVariableLattice(jsScope.getVarCount()); } @Override LiveVariableLattice flowThrough(Node node, LiveVariableLattice input) { final BitSet gen = new BitSet(input.liveSet.size()); final BitSet kill = new BitSet(input.liveSet.size()); // Make kills conditional if the node can end abruptly by an exception. boolean conditional = false; List<DiGraphEdge<Node, Branch>> edgeList = getCfg().getOutEdges(node); for (DiGraphEdge<Node, Branch> edge : edgeList) { if (Branch.ON_EX.equals(edge.getValue())) { conditional = true; } } computeGenKill(node, gen, kill, conditional); LiveVariableLattice result = new LiveVariableLattice(input); // L_in = L_out - Kill + Gen result.liveSet.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>andNot(kill); result.liveSet.or(gen); return result; } /** * Computes the GEN and KILL set. * * @param n Root node. * @param gen Local variables that are live because of the instruction at * {@code n} will be added to this set. * @param kill Local variables that are killed because of the instruction at * {@code n} will be added to this set. * @param conditional {@code true} if any assignments encountered are * conditionally executed. These assignments might not kill a variable. */ private void computeGenKill(Node n, BitSet gen, BitSet kill, boolean conditional) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.FUNCTION: return; case Token.WHILE: case Token.DO: case Token.IF: computeGenKill(NodeUtil.getConditionExpression(n), gen, kill, conditional); return; case Token.FOR: if (!NodeUtil.isForIn(n)) { computeGenKill(NodeUtil.getConditionExpression(n), gen, kill, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (NodeUtil.isVar(lhs)) { // for(var x in y) {...} lhs = lhs.getLastChild(); } addToSetIfLocal(lhs, kill); addToSetIfLocal(lhs, gen); computeGenKill(rhs, gen, kill, conditional); } return; case Token.VAR: for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.hasChildren()) { computeGenKill(c.getFirstChild(), gen, kill, conditional); if (!conditional) { addToSetIfLocal(c, kill); } } } return; case Token.AND: case Token.OR: computeGenKill(n.getFirstChild(), gen, kill, conditional); // May short circuit. computeGenKill(n.getLastChild(), gen, kill, true); return; case Token.HOOK: computeGenKill(n.getFirstChild(), gen, kill, conditional); // Assume both sides are conditional. computeGenKill(n.getFirstChild().getNext(), gen, kill, true); computeGenKill(n.getLastChild(), gen, kill, true); return; case Token.NAME: if (isArgumentsName(n)) { markAllParametersEscaped(); } else { addToSetIfLocal(n, gen); } return; default: if (NodeUtil.isAssignmentOp(n) && NodeUtil.isName(n.getFirstChild())) { Node lhs = n.getFirstChild(); if (!conditional) { addToSetIfLocal(lhs, kill); } if (!NodeUtil

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.isAssign(n)) { // assignments such as a += 1 reads a. addToSetIfLocal(lhs, gen); } computeGenKill(lhs.getNext(), gen, kill, conditional); } else { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { computeGenKill(c, gen, kill, conditional); } } return; } } private void addToSetIfLocal(Node node, BitSet set) { Preconditions.checkState(NodeUtil.isName(node)); String name = node.getString(); if (!jsScope.isDeclared(name, false)) { return; } Var var = jsScope.getVar(name); if (!escaped.contains(var)) { set.set(var.index); } } /** * Give up computing liveness of formal parameter by putting all the parameter * names in the escaped set. */ void markAllParametersEscaped() { Node lp = jsScope.getRootNode().getFirstChild().getNext(); for(Node arg = lp.getFirstChild(); arg != null; arg = arg.getNext()) { escaped.add(jsScope.getVar(arg.getString())); } } private boolean isArgumentsName(Node n) { if (n.getType() != Token.NAME || !n.getString().equals(ARGUMENT_ARRAY_ALIAS) || jsScope.isDeclared(ARGUMENT_ARRAY_ALIAS, false)) { return false; } else { return true; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; import javax.annotation.Nullable; /** * Peephole optimization to remove useless code such as IF's with false * guard conditions, comma operator left hand sides with no side effects, etc. * */ public class PeepholeRemoveDeadCode extends AbstractPeepholeOptimization { // TODO(dcc): Some (all) of these can probably be better achieved // using the control flow graph (like CheckUnreachableCode). // There is an existing CFG pass (UnreachableCodeElimination) that // could be changed to use code from CheckUnreachableCode to do this. @Override Node optimizeSubtree(Node subtree) { switch(subtree.getType()) { case Token.COMMA: return tryFoldComma(subtree); case Token.SCRIPT: case Token.BLOCK: return tryOptimizeBlock(subtree); case Token.EXPR_RESULT: subtree = tryFoldExpr(subtree); return subtree; case Token.HOOK: return tryFoldHook(subtree); case Token.SWITCH: return tryOptimizeSwitch(subtree); case Token.IF: return tryFoldIf(subtree); case Token.WHILE: return tryFoldWhile(subtree); case Token.FOR: { Node condition = NodeUtil.getConditionExpression(subtree); if (condition != null) { tryFoldForCondition(condition); } } return tryFoldFor(subtree); case Token.DO: return tryFoldDo(subtree); default: return subtree; } } /** * Try folding EXPR_RESULT nodes by removing useless Ops and expressions. * @return the replacement node, if changed, or the original if not */ private Node tryFoldExpr(Node subtree) { Node result = trySimpilifyUnusedResult(subtree.getFirstChild()); if (result == null) { Node parent = subtree.getParent(); // If the EXPR_RESULT no longer has any children

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, remove it as well. if (parent.getType() == Token.LABEL) { Node replacement = new Node(Token.BLOCK).copyInformationFrom(subtree); parent.replaceChild(subtree, replacement); subtree = replacement; } else { subtree.detachFromParent(); subtree = null; } } return subtree; } /** * General cascading unused operation node removal. * @param n The root of the expression to simplify. * @return The replacement node, or null if the node was is not useful. */ private Node trySimpilifyUnusedResult(Node n) { return trySimpilifyUnusedResult(n, true); } /** * General cascading unused operation node removal. * @param n The root of the expression to simplify. * @param removeUnused If true, the node is removed from the AST if * it is not useful, otherwise it replaced with an EMPTY node. * @return The replacement node, or null if the node was is not useful. */ private Node trySimpilifyUnusedResult(Node n, boolean removeUnused) { Node result = n; // Simplify the results of conditional expressions switch (n.getType()) { case Token.HOOK: Node trueNode = trySimpilifyUnusedResult(n.getFirstChild().getNext()); Node falseNode = trySimpilifyUnusedResult(n.getLastChild()); // If one or more of the conditional children were removed, // transform the HOOK to an equivalent operation: // x() ? foo() : 1 --> x() && foo() // x() ? 1 : foo() --> x() || foo() // x() ? 1 : 1 --> x() // x ? 1 : 1 --> null if (trueNode == null && falseNode != null) { n.setType(Token.OR); Preconditions.checkState(n.getChildCount() == 2); } else if (trueNode != null && falseNode == null) { n.setType(Token.AND); Preconditions.checkState(n.getChildCount() == 2); } else if (trueNode == null && falseNode == null) { result = trySimpilifyUnusedResult(n.getFirstChild()); } else { // The structure didn't change. result = n; } break; case Token.AND: case Token.OR: // Try to remove the second operand from a AND or OR operations: // x() || f --> x() // x() && f --> x() Node conditionalResultNode = trySimpilifyUnusedResult( n.getLastChild()); if (conditionalResultNode == null) { Preconditions.checkState(n.hasOneChild()); // The conditionally executed code was removed, so // replace the AND/OR with its LHS or remove it if it isn't useful. result = trySimpilifyUnusedResult(n.getFirstChild()); } break;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case Token.FUNCTION: // A function expression isn't useful if it isn't used, remove it and // don't bother to look at its children. result = null; break; case Token.COMMA: // We rewrite other operations as COMMA expressions (which will later // get split into individual EXPR_RESULT statement, if possible), so // we special case COMMA (we don't want to rewrite COMMAs as new COMMAs // nodes. Node left = trySimpilifyUnusedResult(n.getFirstChild()); Node right = trySimpilifyUnusedResult(n.getLastChild()); if (left == null && right == null) { result = null; } else if (left == null) { result = right; } else if (right == null){ result = left; } else { // The structure didn't change. result = n; } break; default: if (!NodeUtil.nodeTypeMayHaveSideEffects(n)) { // This is the meat of this function. The node itself doesn't generate // any side-effects but preserve any side-effects in the children. Node resultList = null; for (Node next, c = n.getFirstChild(); c != null; c = next) { next = c.getNext(); c = trySimpilifyUnusedResult(c); if (c != null) { c.detachFromParent(); if (resultList == null) { // The first side-effect can be used stand-alone. resultList = c; } else { // Leave the side-effects in-place, simplifying it to a COMMA // expression. resultList = new Node(Token.COMMA, resultList, c) .copyInformationFrom(c); } } } result = resultList; } } // Fix up the AST, replace or remove the an unused node (if requested). if (n != result) { Node parent = n.getParent(); if (result == null) { if (removeUnused) { parent.removeChild(n); } else { result = new Node(Token.EMPTY).copyInformationFrom(n); parent.replaceChild(n, result); } } else { // A new COMMA expression may not have an existing parent. if (result.getParent() != null) { result.detachFromParent(); } n.getParent().replaceChild(n, result); } reportCodeChange(); } return result; } /** * Remove useless switches and cases. */ private Node tryOptimizeSwitch(Node n) { Preconditions.checkState(n.getType() == Token.SWITCH); Node defaultCase = tryOptimizeDefaultCase(n); // Removing cases when there exists a default case is not safe. if (defaultCase == null) { Node next = null; Node prev = null; // The first child is the switch conditions skip it. for (Node c

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> = n.getFirstChild().getNext(); c != null; c = next) { next = c.getNext(); if (!mayHaveSideEffects(c.getFirstChild()) && isUselessCase(c, prev)) { removeCase(n, c); } else { prev = c; } } } // Remove the switch if there are no remaining cases. if (n.hasOneChild()) { Node condition = n.removeFirstChild(); Node parent = n.getParent(); Node replacement = new Node(Token.EXPR_RESULT, condition) .copyInformationFrom(n); parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } return null; } /** * @return the default case node or null if there is no default case or * if the default case is removed. */ private Node tryOptimizeDefaultCase(Node n) { Preconditions.checkState(n.getType() == Token.SWITCH); Node lastNonRemovable = n.getFirstChild(); // The switch condition // The first child is the switch conditions skip it when looking for cases. for (Node c = n.getFirstChild().getNext(); c != null; c = c.getNext()) { if (c.getType() == Token.DEFAULT) { // Remove cases that fall-through to the default case Node caseToRemove = lastNonRemovable.getNext(); for (Node next; caseToRemove != c; caseToRemove = next) { next = caseToRemove.getNext(); removeCase(n, caseToRemove); } // Don't use the switch condition as the previous case. Node prevCase = (lastNonRemovable == n.getFirstChild()) ? null : lastNonRemovable; // Remove the default case if we can if (isUselessCase(c, prevCase)) { removeCase(n, c); return null; } return c; } else { Preconditions.checkState(c.getType() == Token.CASE); if (c.getLastChild().hasChildren() || mayHaveSideEffects(c.getFirstChild())) { lastNonRemovable = c; } } } return null; } /** * Remove the case from the switch redeclaring any variables declared in it. * @param caseNode The case to remove. */ private void removeCase(Node switchNode, Node caseNode) { NodeUtil.redeclareVarsInsideBranch(caseNode); switchNode.removeChild(caseNode); reportCodeChange(); } /** * The function assumes that when checking a CASE node there is no * DEFAULT node in the SWITCH. * @return Whether the CASE or DEFAULT block does anything useful. */ private boolean isUselessCase(Node caseNode, @Nullable Node previousCase) { Preconditions.checkState( previousCase == null || previousCase.getNext() == caseNode); // A case isn't useless can't be useless if a previous case falls // through to it unless

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> it happens to be the last case in the switch. Node switchNode = caseNode.getParent(); if (switchNode.getLastChild() != caseNode && previousCase != null) { Node previousBlock = previousCase.getLastChild(); if (!previousBlock.hasChildren() || !isExit(previousBlock.getLastChild())) { return false; } } Node executingCase = caseNode; while (executingCase != null) { Preconditions.checkState(executingCase.getType() == Token.DEFAULT || executingCase.getType() == Token.CASE); // We only expect a DEFAULT case if the case we are checking is the // DEFAULT case. Otherwise we assume the DEFAULT case has already // been removed. Preconditions.checkState(caseNode == executingCase || executingCase.getType() != Token.DEFAULT); Node block = executingCase.getLastChild(); Preconditions.checkState(block.getType() == Token.BLOCK); if (block.hasChildren()) { for (Node blockChild : block.children()) { int type = blockChild.getType(); // If this is a block with a labelless break, it is useless. switch (blockChild.getType()) { case Token.BREAK: // A break to a different control structure isn't useless. return blockChild.getFirstChild() == null; case Token.VAR: if (blockChild.hasOneChild() && blockChild.getFirstChild().getFirstChild() == null) { // Variable declarations without initializations are ok. continue; } return false; default: return false; } } } else { // Look at the fallthrough case executingCase = executingCase.getNext(); } } return true; } /** * @return Whether the node is an obvious control flow exit. */ private boolean isExit(Node n) { switch (n.getType()) { case Token.BREAK: case Token.CONTINUE: case Token.RETURN: case Token.THROW: return true; default: return false; } } private Node tryFoldComma(Node n) { // If the left side does nothing replace the comma with the result. Node parent = n.getParent(); Node left = n.getFirstChild(); Node right = left.getNext(); left = trySimpilifyUnusedResult(left); if (left == null || !mayHaveSideEffects(left)) { // Fold it! n.removeChild(right); parent.replaceChild(n, right); reportCodeChange(); return right; } else { if (parent.getType() == Token.EXPR_RESULT && parent.getParent().getType() != Token.LABEL) { // split comma n.detachChildren(); // Replace the original expression with the left operand. parent.replaceChild(n, left); // Add the right expression afterward. Node newStatement = new Node(Token.EXPR_RESULT, right); newStatement.copyInformationFrom(n

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>value != TernaryValue.UNKNOWN) { int replacementConditionNodeType = (value.toBoolean(true)) ? Token.TRUE : Token.FALSE; condition.getParent().replaceChild(condition, new Node(replacementConditionNodeType)); reportCodeChange(); } } } } /** * @return whether the node is a assignment to a simple name, or simple var * declaration with initialization. */ private boolean isSimpleAssignment(Node n) { // For our purposes we define a simple assignment to be a assignment // to a NAME node, or a VAR declaration with one child and a initializer. if (NodeUtil.isExprAssign(n) && NodeUtil.isName(n.getFirstChild().getFirstChild())) { return true; } else if (n.getType() == Token.VAR && n.hasOneChild() && n.getFirstChild().getFirstChild() != null) { return true; } return false; } /** * @return The name being assigned to. */ private Node getSimpleAssignmentName(Node n) { Preconditions.checkState(isSimpleAssignment(n)); if (NodeUtil.isExprAssign(n)) { return n.getFirstChild().getFirstChild(); } else { // A var declaration. return n.getFirstChild(); } } /** * @return The value assigned in the simple assignment */ private Node getSimpleAssignmentValue(Node n) { Preconditions.checkState(isSimpleAssignment(n)); return n.getFirstChild().getLastChild(); } /** * @return Whether the node is a conditional statement. */ private boolean isConditionalStatement(Node n) { // We defined a conditional statement to be a IF or EXPR_RESULT rooted with // a HOOK, AND, or OR node. return n != null && (n.getType() == Token.IF || isExprConditional(n)); } /** * @return Whether the node is a rooted with a HOOK, AND, or OR node. */ private boolean isExprConditional(Node n) { if (n.getType() == Token.EXPR_RESULT) { switch (n.getFirstChild().getType()) { case Token.HOOK: case Token.AND: case Token.OR: return true; } } return false; } /** * @return The condition of a conditional statement. */ private Node getConditionalStatementCondition(Node n) { if (n.getType() == Token.IF) { return NodeUtil.getConditionExpression(n); } else { Preconditions.checkState(isExprConditional(n)); return n.getFirstChild().getFirstChild(); } } /** * Try folding IF nodes by removing dead branches. * @return the replacement node, if changed, or the original if not */ private Node tryFoldIf(Node n) { Preconditions.checkState(n.getType() == Token.IF); Node parent = n.getParent

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(); Preconditions.checkNotNull(parent); int type = n.getType(); Node cond = n.getFirstChild(); Node thenBody = cond.getNext(); Node elseBody = thenBody.getNext(); // if (x) { .. } else { } --> if (x) { ... } if (elseBody != null && !mayHaveSideEffects(elseBody)) { n.removeChild(elseBody); elseBody = null; reportCodeChange(); } // if (x) { } else { ... } --> if (!x) { ... } if (!mayHaveSideEffects(thenBody) && elseBody != null) { n.removeChild(elseBody); n.replaceChild(thenBody, elseBody); Node notCond = new Node(Token.NOT); n.replaceChild(cond, notCond); notCond.addChildToFront(cond); cond = notCond; thenBody = cond.getNext(); elseBody = null; reportCodeChange(); } // if (x()) { } if (!mayHaveSideEffects(thenBody) && elseBody == null) { if (mayHaveSideEffects(cond)) { // x() has side effects, just leave the condition on its own. n.removeChild(cond); Node replacement = NodeUtil.newExpr(cond); parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } else { // x() has no side effects, the whole tree is useless now. NodeUtil.removeChild(parent, n); reportCodeChange(); return null; } } // Try transforms that apply to both IF and HOOK. TernaryValue condValue = NodeUtil.getExpressionBooleanValue(cond); if (condValue == TernaryValue.UNKNOWN) { return n; // We can't remove branches otherwise! } if (mayHaveSideEffects(cond)) { // Transform "if (a = 2) {x =2}" into "if (true) {a=2;x=2}" boolean newConditionValue = condValue == TernaryValue.TRUE; // Add an elseBody if it is needed. if (!newConditionValue && elseBody == null) { elseBody = new Node(Token.BLOCK).copyInformationFrom(n); n.addChildToBack(elseBody); } Node newCond = new Node(newConditionValue ? Token.TRUE : Token.FALSE); n.replaceChild(cond, newCond); Node branchToKeep = newConditionValue ? thenBody : elseBody; branchToKeep.addChildToFront( new Node(Token.EXPR_RESULT, cond).copyInformationFrom(cond)); reportCodeChange(); cond = newCond; } boolean condTrue = condValue.toBoolean(true); if (n.getChildCount() == 2) { Preconditions.checkState(type == Token.IF); if (condTrue) { // Replace "if (true) { X }" with "X

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>". Node thenStmt = n.getFirstChild().getNext(); n.removeChild(thenStmt); parent.replaceChild(n, thenStmt); reportCodeChange(); return thenStmt; } else { // Remove "if (false) { X }" completely. NodeUtil.redeclareVarsInsideBranch(n); NodeUtil.removeChild(parent, n); reportCodeChange(); return null; } } else { // Replace "if (true) { X } else { Y }" with X, or // replace "if (false) { X } else { Y }" with Y. Node trueBranch = n.getFirstChild().getNext(); Node falseBranch = trueBranch.getNext(); Node branchToKeep = condTrue ? trueBranch : falseBranch; Node branchToRemove = condTrue ? falseBranch : trueBranch; NodeUtil.redeclareVarsInsideBranch(branchToRemove); n.removeChild(branchToKeep); parent.replaceChild(n, branchToKeep); reportCodeChange(); return branchToKeep; } } /** * Try folding HOOK (?:) if the condition results of the condition is known. * @return the replacement node, if changed, or the original if not */ private Node tryFoldHook(Node n) { Preconditions.checkState(n.getType() == Token.HOOK); Node parent = n.getParent(); Preconditions.checkNotNull(parent); Node cond = n.getFirstChild(); Node thenBody = cond.getNext(); Node elseBody = thenBody.getNext(); TernaryValue condValue = NodeUtil.getExpressionBooleanValue(cond); if (condValue == TernaryValue.UNKNOWN) { return n; // We can't remove branches otherwise! } // Transform "(a = 2) ? x =2 : y" into "a=2,x=2" n.detachChildren(); Node branchToKeep = condValue.toBoolean(true) ? thenBody : elseBody; Node replacement; if (mayHaveSideEffects(cond)) { replacement = new Node(Token.COMMA).copyInformationFrom(n); replacement.addChildToFront(cond); replacement.addChildToBack(branchToKeep); } else { replacement = branchToKeep; } parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } /** * Removes WHILEs that always evaluate to false. */ Node tryFoldWhile(Node n) { Preconditions.checkArgument(n.getType() == Token.WHILE); Node cond = NodeUtil.getConditionExpression(n); if (NodeUtil.getBooleanValue(cond) != TernaryValue.FALSE) { return n; } NodeUtil.redeclareVarsInsideBranch(n); NodeUtil.removeChild(n.getParent(), n); reportCodeChange(); return null; } /** * Removes FORs that always evaluate to false. */ Node tryFoldFor(Node n) { Preconditions.check

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Argument(n.getType() == Token.FOR); // If this is a FOR-IN loop skip it. if (NodeUtil.isForIn(n)) { return n; } Node init = n.getFirstChild(); Node cond = init.getNext(); Node increment = cond.getNext(); if (init.getType() != Token.EMPTY && init.getType() != Token.VAR) { init = trySimpilifyUnusedResult(init, false); } if (increment.getType() != Token.EMPTY) { increment = trySimpilifyUnusedResult(increment, false); } // There is an initializer skip it if (n.getFirstChild().getType() != Token.EMPTY) { return n; } if (NodeUtil.getBooleanValue(cond) != TernaryValue.FALSE) { return n; } NodeUtil.redeclareVarsInsideBranch(n); NodeUtil.removeChild(n.getParent(), n); reportCodeChange(); return null; } /** * Removes DOs that always evaluate to false. This leaves the * statements that were in the loop in a BLOCK node. * The block will be removed in a later pass, if possible. */ Node tryFoldDo(Node n) { Preconditions.checkArgument(n.getType() == Token.DO); Node cond = NodeUtil.getConditionExpression(n); if (NodeUtil.getBooleanValue(cond) != TernaryValue.FALSE) { return n; } // TODO(johnlenz): The do-while can be turned into a label with // named breaks and the label optimized away (maybe). if (hasBreakOrContinue(n)) { return n; } Preconditions.checkState( NodeUtil.isControlStructureCodeBlock(n, n.getFirstChild())); Node block = n.removeFirstChild(); n.getParent().replaceChild(n, block); reportCodeChange(); return n; } /** * */ boolean hasBreakOrContinue(Node n) { // TODO(johnlenz): This is overkill as named breaks may refer to outer // loops or labels, and any break my refer to an inner loop. // More generally, this check may be more expensive than we like. return NodeUtil.has( n, Predicates.<Node>or( new NodeUtil.MatchNodeType(Token.BREAK), new NodeUtil.MatchNodeType(Token.CONTINUE)), new NodeUtil.MatchNotFunction()); } /** * Remove always true loop conditions. */ private void tryFoldForCondition(Node forCondition) { if (NodeUtil.getBooleanValue(forCondition) == TernaryValue.TRUE) { forCondition.getParent().replaceChild(forCondition, new Node(Token.EMPTY)); reportCodeChange(); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private Node synthesizedExternsRoot = null; // Vars that still need to be declared in externs. These will be declared // at the end of the pass, or when we see the equivalent var declared // in the normal code. private Set<String> varsToDeclareInExterns = Sets.newHashSet(); private final AbstractCompiler compiler; // Whether this is the post-processing sanity check. private final boolean sanityCheck; // Whether extern checks emit error. private boolean strictExternCheck; VarCheck(AbstractCompiler compiler) { this(compiler, false); } VarCheck(AbstractCompiler compiler, boolean sanityCheck) { this.compiler = compiler; this.strictExternCheck = compiler.getErrorLevel( JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR; this.sanityCheck = sanityCheck; } @Override public void process(Node externs, Node root) { // Don't run externs-checking in sanity check mode. Normalization will // remove duplicate VAR declarations, which will make // externs look like they have assigns. if (!sanityCheck) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); } NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); for (String varName : varsToDeclareInExterns) { createSynthesizedExternVar(varName); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() != Token.NAME) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(NodeUtil.isFunction(parent)); // A function declaration with an empty name passes Rhino, // but is supposed to be a syntax error according to the spec. if (!NodeUtil.isFunctionExpression(parent)) { t.report(n, INVALID_FUNCTION_DECL); } return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.getType() == Token.VAR || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); parent.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> || !t.getInput().isExtern()) { t.report(n, UNDEFINED_VAR_ERROR, varName); } if (sanityCheck) { throw new IllegalStateException("Unexpected variable " + varName); } else { createSynthesizedExternVar(varName); scope.getGlobalScope().declare(varName, n, null, getSynthesizedExternsInput()); } } return; } CompilerInput currInput = t.getInput(); CompilerInput varInput = var.input; if (currInput == varInput || currInput == null || varInput == null) { // The variable was defined in the same file. This is fine. return; } // Check module dependencies. JSModule currModule = currInput.getModule(); JSModule varModule = varInput.getModule(); JSModuleGraph moduleGraph = compiler.getModuleGraph(); if (varModule != currModule && varModule != null && currModule != null) { if (moduleGraph.dependsOn(currModule, varModule)) { // The module dependency was properly declared. } else { if (!sanityCheck && scope.isGlobal()) { if (moduleGraph.dependsOn(varModule, currModule)) { // The variable reference violates a declared module dependency. t.report(n, VIOLATED_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } else { // The variable reference is between two modules that have no // dependency relationship. This should probably be considered an // error, but just issue a warning for now. t.report(n, MISSING_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } else { t.report(n, STRICT_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } } } /** * Create a new variable in a synthetic script. This will prevent * subsequent compiler passes from crashing. */ private void createSynthesizedExternVar(String varName) { Node nameNode = Node.newString(Token.NAME, varName); // Mark the variable as constant if it matches the coding convention // for constant vars. // NOTE(nicksantos): honestly, i'm not sure how much this matters. // AFAIK, all people who use the CONST coding convention also // compile with undeclaredVars as errors. We have some test // cases for this configuration though, and it makes them happier. if (compiler.getCodingConvention().isConstant(varName)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } getSynthesizedExternsRoot().addChildToBack( new Node(Token.VAR, nameNode)); varsToDeclareInExterns.remove(varName); } /** * A check for name references in the externs inputs. These used to prevent * a

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> variable from getting renamed, but no longer have any effect. */ private class NameRefInExternsCheck extends AbstractPostOrderCallback { public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { switch (parent.getType()) { case Token.VAR: case Token.FUNCTION: case Token.LP: // These are okay. break; case Token.GETPROP: if (n == parent.getFirstChild()) { Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString()); varsToDeclareInExterns.add(n.getString()); } } break; default: t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString()); Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { varsToDeclareInExterns.add(n.getString()); } break; } } } } /** Lazily create a "new" externs input for undeclared variables. */ private CompilerInput getSynthesizedExternsInput() { if (synthesizedExternsInput == null) { synthesizedExternsInput = compiler.newExternInput("{SyntheticVarsDeclar}"); } return synthesizedExternsInput; } /** Lazily create a "new" externs root for undeclared variables. */ private Node getSynthesizedExternsRoot() { if (synthesizedExternsRoot == null) { CompilerInput synthesizedExterns = getSynthesizedExternsInput(); synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler); } return synthesizedExternsRoot; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(file)); } /** Adds a source file input to this module. */ public void addFirst(JSSourceFile file) { addFirst(new CompilerInput(file)); } /** Adds a source code input to this module. */ public void add(CompilerInput input) { inputs.add(input); input.setModule(this); } /** Adds a source code input to this module. */ public void addFirst(CompilerInput input) { inputs.add(0, input); input.setModule(this); } /** Adds a source code input to this module directly after other. */ public void addAfter(CompilerInput input, CompilerInput other) { Preconditions.checkState(inputs.contains(other)); inputs.add(inputs.indexOf(other), input); input.setModule(this); } /** Adds a dependency on another module. */ public void addDependency(JSModule dep) { Preconditions.checkState(dep != this); deps.add(dep); } /** Removes an input from this module. */ public void remove(CompilerInput input) { input.setModule(null); inputs.remove(input); } /** Removes all of the inputs from this module. */ public void removeAll() { for (CompilerInput input : inputs) { input.setModule(null); } inputs.clear(); } /** * Gets the list of modules that this module depends on. * * @return A list that may be empty but not null */ public List<JSModule> getDependencies() { return deps; } /** * Gets the names of the modules that this module depends on, * sorted alphabetically. */ List<String> getSortedDependencyNames() { List<String> names = Lists.newArrayList(); for (JSModule module : getDependencies()) { names.add(module.getName()); } Collections.sort(names); return names; } /** * Returns the transitive closure of dependencies starting from the * dependencies of this module. */ public Set<JSModule> getAllDependencies() { Set<JSModule> allDeps = Sets.newHashSet(deps); List<JSModule> workList = Lists.newArrayList(deps); while (workList.size() > 0) { JSModule module = workList.remove(workList.size() - 1); for (JSModule dep : module.getDependencies()) { if (allDeps.add(dep)) { workList.add(dep); } } } return allDeps; } /** Returns this module and all of its dependencies in one list. */ public Set<JSModule> getThisAndAllDependencies() { Set<JSModule> deps = getAllDependencies(); deps.add(this); return deps; } /** * Gets this module's list of source code inputs. * * @return A list that may be empty but not null */ public

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> this.oldName = name; this.newName = null; this.count = 0; // Represents the order at which a symbol appears in the source. this.orderOfOccurrence = assignmentCount++; } /** * Assigns the new name. */ void setNewName(String newName) { Preconditions.checkState(this.newName == null); this.newName = newName; } } /** Maps an old name to a new name assignment */ private final SortedMap<String, Assignment> assignments = new TreeMap<String, Assignment>(); /** Whether renaming should apply to local variables only. */ private final boolean localRenamingOnly; /** * Whether function expression names should be preserved. Typically, for * debugging purposes. * @see NameAnonymousFunctions */ private boolean preserveFunctionExpressionNames; /** Characters that shouldn't be used in variable names. */ private final char[] reservedCharacters; /** A prefix to distinguish temporary local names from global names */ private static final String LOCAL_VAR_PREFIX = "L "; RenameVars(AbstractCompiler compiler, String prefix, boolean localRenamingOnly, boolean preserveFunctionExpressionNames, boolean generatePseudoNames, VariableMap prevUsedRenameMap, @Nullable char[] reservedCharacters, @Nullable Set<String> reservedNames) { this.compiler = compiler; this.prefix = prefix == null ? "" : prefix; this.localRenamingOnly = localRenamingOnly; this.preserveFunctionExpressionNames = preserveFunctionExpressionNames; if (generatePseudoNames) { this.pseudoNameMap = Maps.newHashMap(); } else { this.pseudoNameMap = null; } this.prevUsedRenameMap = prevUsedRenameMap; this.reservedCharacters = reservedCharacters; if (reservedNames == null) { this.reservedNames = Sets.newHashSet(); } else { this.reservedNames = Sets.newHashSet(reservedNames); } } /** * Iterate through the nodes, collect all the NAME nodes that need to be * renamed, and count how many times each variable name is referenced. * * There are 2 passes: * - externs: keep track of the global vars in the externNames_ map. * - source: keep track of all name references in globalNameNodes_, and * localNameNodes_. * * To get shorter local variable renaming, we rename local variables to a * temporary name "LOCAL_VAR_PREFIX + index" where index is the index of the * variable declared in the local scope stack. * e.g. * Foo(fa, fb) { * var c = function(d, e) { return fa; } * } * The indexes are: fa:0, fb:1, c:2, d:3, e:4 * * In that way, local variable names are reused in each global function. * e.g. the final code might look like * function x(a

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>,b) { ... } * function y(a,b,c) { ... } */ class ProcessVars extends AbstractPostOrderCallback { private final boolean isExternsPass_; ProcessVars(boolean isExterns) { isExternsPass_ = isExterns; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() != Token.NAME) { return; } String name = n.getString(); // Ignore anonymous functions if (name.length() == 0) { return; } // Is this local or Global? Scope.Var var = t.getScope().getVar(name); boolean local = (var != null) && var.isLocal(); // Are we renaming global variables? if (!local && localRenamingOnly) { reservedNames.add(name); return; } // Are we renaming function expression names? if (preserveFunctionExpressionNames && var != null && NodeUtil.isFunctionExpression(var.getParentNode())) { reservedNames.add(name); return; } // Check if we can rename this. if (!okToRenameVar(name, local)) { if (local) { // Blindly de-uniquify for the Prototype library for issue 103. String newName = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName( name); if (!newName.equals(name)) { n.setString(newName); } } return; } if (isExternsPass_) { // Keep track of extern globals. if (!local) { externNames.add(name); } return; } if (pseudoNameMap != null) { recordPseudoName(n); } if (local) { // Local var: assign a new name String tempName = LOCAL_VAR_PREFIX + var.getLocalVarIndex(); incCount(tempName, null); localNameNodes.add(n); n.setString(tempName); } else if (var != null) { // Not an extern // If it's global, increment global count incCount(name, var.input); globalNameNodes.add(n); } } // Increment count of an assignment void incCount(String name, CompilerInput input) { Assignment s = assignments.get(name); if (s == null) { s = new Assignment(name, input); assignments.put(name, s); } s.count++; } } /** * Sorts Assignment objects by their count, breaking ties by their * order of occurrence in the source to ensure a deterministic total * ordering. */ private static final Comparator<Assignment> FREQUENCY_COMPARATOR = new Comparator<Assignment>() { public int compare(Assignment a1, Assignment a2) { if (a1.count != a

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * * The test for Var equality uses reference equality, so it's necessary to * inject a scope when you traverse. */ ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior, Predicate<Var> varFilter) { this.compiler = compiler; this.behavior = behavior; this.varFilter = varFilter; } /** * Convenience method for running this pass over a tree with this * class as a callback. */ public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } /** * Gets the variables that were referenced in this callback. */ public Set<Var> getReferencedVariables() { return referenceMap.keySet(); } /** * Gets the reference collection for the given variable. */ public ReferenceCollection getReferenceCollection(Var v) { return referenceMap.get(v); } /** * For each node, update the block stack and reference collection * as appropriate. */ public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { Var v = t.getScope().getVar(n.getString()); if (v != null && varFilter.apply(v)) { addReference(t, v, new Reference(n, parent, t, blockStack.peek())); } } if (isBlockBoundary(n, parent)) { blockStack.pop(); } } /** * Updates block stack and invokes any additional behavior. */ public void enterScope(NodeTraversal t) { Node n = t.getScope().getRootNode(); BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek(); blockStack.push(new BasicBlock(parent, n)); } /** * Updates block statck and invokes any additional behavior. */ public void exitScope(NodeTraversal t) { blockStack.pop(); behavior.afterExitScope(t, referenceMap); } /** * Updates block stack. */ public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // If node is a new basic block, put on basic block stack if (isBlockBoundary(n, parent)) { blockStack.push(new BasicBlock(blockStack.peek(), n)); } return true; } /** * @return true if this node marks the start of a new basic block */ private static boolean isBlockBoundary(Node n, Node parent) { if (parent != null) { switch (parent.getType()) { case Token.DO: case Token.FOR: case Token.TRY: case Token.WHILE: case Token.WITH: // NOTE: TRY has up to 3 child blocks: // TRY // BLOCK // BLOCK // CATCH // BLOCK // Note that there is an explcit CATCH token but no explicit // FINALLY token. For simplicity

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, we consider each BLOCK // a separate basic BLOCK. return true; case Token.AND: case Token.HOOK: case Token.IF: case Token.OR: // The first child of a conditional is not a boundary, // but all the rest of the children are. return n != parent.getFirstChild(); } } return n.getType() == Token.CASE; } private void addReference(NodeTraversal t, Var v, Reference reference) { // Create collection if none already ReferenceCollection referenceInfo = referenceMap.get(v); if (referenceInfo == null) { referenceInfo = new ReferenceCollection(); referenceMap.put(v, referenceInfo); } // Add this particular reference referenceInfo.add(reference, t, v); } /** * Way for callers to add specific behavior during traversal that * utilizes the built-up reference information. */ interface Behavior { /** * Called after we finish with a scope. */ void afterExitScope(NodeTraversal t, Map<Var, ReferenceCollection> referenceMap); } static Behavior DO_NOTHING_BEHAVIOR = new Behavior() { @Override public void afterExitScope(NodeTraversal t, Map<Var, ReferenceCollection> referenceMap) {} }; /** * A collection of references. Can be subclassed to apply checks or * store additional state when adding. */ static class ReferenceCollection { List<Reference> references = Lists.newArrayList(); void add(Reference reference, NodeTraversal t, Var v) { references.add(reference); } /** * Determines if the variable for this reference collection is * "well-defined." A variable is well-defined if we can prove at * compile-time that it's assigned a value before it's used. * * Notice that if this function returns false, this doesn't imply that the * variable is used before it's assigned. It just means that we don't * have enough information to make a definitive judgement. */ protected boolean isWellDefined() { int size = references.size(); if (size == 0) { return false; } // If this is a declaration that does not instantiate the variable, // it's not well-defined. Reference init = getInitializingReference(); if (init == null) { return false; } Preconditions.checkState(references.get(0).isDeclaration()); BasicBlock initBlock = init.getBasicBlock(); for (int i = 1; i < size; i++) { if (!initBlock.provablyExecutesBefore( references.get(i).getBasicBlock())) { return false; } } return true; } /** * Whether the variable is escaped into an inner scope. */ boolean isEscaped() { Scope scope = null; for (Reference ref : references) { if (scope == null) { scope = ref.scope; } else

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (scope != ref.scope) { return true; } } return false; } /** * @param index The index into the references array to look for an * assigning declaration. * * This is either the declaration if a value is assigned (such as * "var a = 2", "function a()...", "... catch (a)..."). */ private boolean isInitializingDeclarationAt(int index) { Reference maybeInit = references.get(index); if (maybeInit.isInitializingDeclaration()) { // This is a declaration that represents the initial value. // Specifically, var declarations without assignments such as "var a;" // are not. return true; } return false; } /** * @param index The index into the references array to look for an * initialized assignment reference. That is, an assignment immediately * follow a variable declaration that itself does not initialize the * variable. */ private boolean isInitializingAssignmentAt(int index) { if (index < references.size() && index > 0) { Reference maybeDecl = references.get(index-1); if (maybeDecl.isVarDeclaration()) { Preconditions.checkState(!maybeDecl.isInitializingDeclaration()); Reference maybeInit = references.get(index); if (maybeInit.isSimpleAssignmentToName()) { return true; } } } return false; } /** * @return The reference that provides the value for the variable at the * time of the first read, if known, otherwise null. * * This is either the variable declaration ("var a = ...") or first * reference following the declaration if it is an assignment. */ Reference getInitializingReference() { if (isInitializingDeclarationAt(0)) { return references.get(0); } else if (isInitializingAssignmentAt(1)) { return references.get(1); } return null; } /** * Constants are allowed to be defined after their first use. */ Reference getInitializingReferenceForConstants() { int size = references.size(); for (int i = 0; i < size; i++) { if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) { return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * assignments. */ private Reference getOneAndOnlyAssignment() { Reference assignment = null; int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { if (assignment == null) { assignment = ref; } else { return null; } } } return assignment; } /** * @return Whether the variable is never assigned a value. */ boolean isNeverAssigned() { int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { return false; } } return true; } boolean firstReferenceIsAssigningDeclaration() { int size = references.size(); if (size > 0 && references.get(0).isInitializingDeclaration()) { return true; } return false; } } /** * Represents a single declaration or reference to a variable. */ static final class Reference { private static final Set<Integer> DECLARATION_PARENTS = ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH); private final Node nameNode; private final Node parent; private final Node grandparent; private final BasicBlock basicBlock; private final Scope scope; private final String sourceName; Reference(Node nameNode, Node parent, NodeTraversal t, BasicBlock basicBlock) { this(nameNode, parent, parent.getParent(), basicBlock, t.getScope(), t.getSourceName()); } // Bleeding functions are weird, because the declaration does // not appear inside their scope. So they need their own constructor. static Reference newBleedingFunction(NodeTraversal t, BasicBlock basicBlock, Node func) { return new Reference(func.getFirstChild(), func, func.getParent(), basicBlock, t.getScope(), t.getSourceName()); } private Reference(Node nameNode, Node parent, Node grandparent, BasicBlock basicBlock, Scope scope, String sourceName) { this.nameNode = nameNode; this.parent = parent; this.grandparent = grandparent; this.basicBlock = basicBlock; this.scope = scope; this.sourceName = sourceName; } boolean isDeclaration() { return DECLARATION_PARENTS.contains(parent.getType()) || parent.getType() == Token.LP && grandparent.getType() == Token.FUNCTION; } boolean isVarDeclaration() { return parent.getType() == Token.VAR; } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(parent); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && (parent.getType() != Token.VAR || nameNode.getFirstChild() != null); } /** * @return For an assignment, variable declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { return (parent.getType() == Token.FUNCTION) ? parent : NodeUtil.getAssignedValue(getNameNode()); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return parent; } Node getNameNode() { return nameNode; } Node getGrandparent() { return grandparent; } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.getType() == Token.VAR) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { return parent.getType() == Token.ASSIGN && parent.getFirstChild() == nameNode; } boolean isLvalue() { int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); } Scope getScope() { return scope; } public String getSourceName() { return sourceName; } } /** * Represents a section of code that is uninterrupted by control structures * (conditional or iterative logic). */ static final class BasicBlock { private final BasicBlock parent; /** * Determines whether the block may not be part of the normal control flow, * but instead "hoisted" to the top of the scope. */ private final boolean isHoisted; /** * Whether this block denotes a function scope. */ private final boolean isFunction; /** * Whether this block denotes a loop. */ private final boolean isLoop; /** * Creates a new block. * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; // only named functions may be hoisted. this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root); this.isFunction = root.getType() == Token.FUNCTION; if (root.getParent() != null) { int pType = root.getParent().getType(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ReverseAbstractInterpreter nextLink; /** * Constructs an interpreter, which is the only link in a chain. Interpreters * can be appended using {@link #append}. */ ChainableReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { Preconditions.checkNotNull(convention); this.convention = convention; this.typeRegistry = typeRegistry; firstLink = this; nextLink = null; } /** * Appends a link to {@code this}, returning the updated last link. * <p> * The pattern {@code new X().append(new Y())...append(new Z())} forms a * chain starting with X, then Y, then ... Z. * @param lastLink a chainable interpreter, with no next link * @return the updated last link */ ChainableReverseAbstractInterpreter append( ChainableReverseAbstractInterpreter lastLink) { Preconditions.checkArgument(lastLink.nextLink == null); this.nextLink = lastLink; lastLink.firstLink = this.firstLink; return lastLink; } /** * Gets the first link of this chain. */ ChainableReverseAbstractInterpreter getFirst() { return firstLink; } /** * Calculates the preciser scope starting with the first link. */ protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return firstLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } /** * Delegates the calculation of the preciser scope to the next link. * If there is no next link, returns the blind scope. */ protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome) : blindScope; } /** * Returns the type of a node in the given scope if the node corresponds to a * name whose type is capable of being refined. * @return The current type of the node if it can be refined, null otherwise. */ JSType getTypeIfRefinable(Node node, FlowScope scope) { switch (node.getType()) { case Token.NAME: StaticSlot<JSType> nameVar = scope.getSlot(node.getString()); if (nameVar != null) { JSType nameVarType = nameVar.getType(); if (nameVarType == null) { nameVarType = node.getJSType(); } return nameVarType; } return null; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); if (qualifiedName == null) { return null; } StaticSlot<JSType> propVar = scope.getSlot(qualifiedName); JSType propVarType = null; if (propVar != null) { propVarType =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> propVar.getType(); } if (propVarType == null) { propVarType = node.getJSType(); } if (propVarType == null) { propVarType = getNativeType(UNKNOWN_TYPE); } return propVarType; } return null; } /** * Declares a refined type in {@code scope} for the name represented by * {@code node}. It must be possible to refine the type of the given node in * the given scope, as determined by {@link #getTypeIfRefinable}. */ protected void declareNameInScope(FlowScope scope, Node node, JSType type) { switch (node.getType()) { case Token.NAME: scope.inferSlotType(node.getString(), type); break; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); Preconditions.checkNotNull(qualifiedName); JSType origType = node.getJSType(); origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType; scope.inferQualifiedSlot(qualifiedName, origType, type); break; default: throw new IllegalArgumentException("Node cannot be refined. \n" + node.toStringTree()); } } /** * @see #getRestrictedWithoutUndefined(JSType) */ private final Visitor<JSType> restrictUndefinedVisitor = new Visitor<JSType>() { public JSType caseEnumElementType(EnumElementType enumElementType) { JSType type = enumElementType.getPrimitiveType().visit(this); if (type != null && enumElementType.getPrimitiveType().equals(type)) { return enumElementType; } else { return type; } } public JSType caseAllType() { return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE); } public JSType caseNoObjectType() { return getNativeType(NO_OBJECT_TYPE); } public JSType caseNoType() { return getNativeType(NO_TYPE); } public JSType caseBooleanType() { return getNativeType(BOOLEAN_TYPE); } public JSType caseFunctionType(FunctionType type) { return type; } public JSType caseNullType() { return getNativeType(NULL_TYPE); } public JSType caseNumberType() { return getNativeType(NUMBER_TYPE); } public JSType caseObjectType(ObjectType type) { return type; } public JSType caseStringType() { return getNativeType(STRING_TYPE); } public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); } public JSType caseUnknownType() { return getNativeType(UNKNOWN_TYPE); } public JSType caseVoidType() { return null; } }; /** * @see #getRestrictedWithoutNull

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, CheckLevel reportUnknownTypes) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.reverseInterpreter = reverseInterpreter; this.typeRegistry = typeRegistry; this.topScope = topScope; this.scopeCreator = scopeCreator; this.reportMissingOverride = reportMissingOverride; this.reportUnknownTypes = reportUnknownTypes; this.inferJSDocInfo = new InferJSDocInfo(compiler); } public TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry, CheckLevel reportMissingOverride, CheckLevel reportUnknownTypes) { this(compiler, reverseInterpreter, typeRegistry, null, null, reportMissingOverride, reportUnknownTypes); } TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry) { this(compiler, reverseInterpreter, typeRegistry, null, null, CheckLevel.WARNING, CheckLevel.OFF); } /** Turn on the missing property check. Returns this for easy chaining. */ TypeCheck reportMissingProperties(boolean report) { reportMissingProperties = report; return this; } /** * Main entry point for this phase of processing. This follows the pattern for * JSCompiler phases. * * @param externsRoot The root of the externs parse tree. * @param jsRoot The root of the input parse tree to be checked. */ public void process(Node externsRoot, Node jsRoot) { Preconditions.checkNotNull(scopeCreator); Preconditions.checkNotNull(topScope); Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); if (externsRoot != null) { check(externsRoot, true); } check(jsRoot, false); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.process(node, null); } else { inferJSDocInfo.process(null, node); } } private void checkNoTypeCheckSection(Node n, boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final TypeCheck outerThis = this; final Scope outerScope = t.getScope(); final FunctionType functionType = (FunctionType) n.getJSType(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName); } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.NAME: typeable = visitName(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>t, n, parent); break; case Token.LP: // If this is under a FUNCTION node, it is a parameter list and can be // ignored here. if (parent.getType() != Token.FUNCTION) { ensureTyped(t, n, getJSType(n.getFirstChild())); } else { typeable = false; } break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis()); break; case Token.REF_SPECIAL: ensureTyped(t, n); break; case Token.GET_REF: ensureTyped(t, n, getJSType(n.getFirstChild())); break; case Token.NULL: ensureTyped(t, n, NULL_TYPE); break; case Token.NUMBER: if (n.getParent().getType() != Token.OBJECTLIT) { ensureTyped(t, n, NUMBER_TYPE); } else { typeable = false; } break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.STRING: if (n.getParent().getType() != Token.OBJECTLIT) { ensureTyped(t, n, STRING_TYPE); } else { typeable = false; } break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.getType() == Token.ASSIGN && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown, so no point counting that. // If that unknown leaks elsewhere (say by an assignment to another // variable), then it will be counted. typeable = false; break; case Token.VAR: visitVar(t, n); typeable = false; break; case Token.NEW: visitNew(t, n); typeable = true; break; case Token.CALL: visitCall(t, n); typeable = !NodeUtil.isExpressionNode(parent); break; case Token.RETURN: visitReturn(t, n); typeable = false; break; case Token.DEC: case Token.INC: left = n.getFirstChild(); validator.expectNumber( t, left, getJSType(left), "increment/decrement"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.NOT: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.VOID:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> ensureTyped(t, n, VOID_TYPE); break; case Token.TYPEOF: ensureTyped(t, n, STRING_TYPE); break; case Token.BITNOT: childType = getJSType(n.getFirstChild()); if (!childType.matchesInt32Context()) { report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()), childType.toString()); } ensureTyped(t, n, NUMBER_TYPE); break; case Token.POS: case Token.NEG: left = n.getFirstChild(); validator.expectNumber(t, left, getJSType(left), "sign operator"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.EQ: case Token.NE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); TernaryValue result = leftTypeRestricted.testForEquality(rightTypeRestricted); if (result != TernaryValue.UNKNOWN) { if (n.getType() == Token.NE) { result = result.not(); } report(t, n, DETERMINISTIC_TEST, leftType.toString(), rightType.toString(), result.toString()); } ensureTyped(t, n, BOOLEAN_TYPE); break; } case Token.SHEQ: case Token.SHNE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); if (!leftTypeRestricted.canTestForShallowEqualityWith( rightTypeRestricted)) { report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(), rightType.toString()); } ensureTyped(t, n, BOOLEAN_TYPE); break; } case Token.LT: case Token.LE: case Token.GT: case Token.GE: leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); if (rightType.isNumber()) { validator.expectNumber( t, n, leftType, "left side of numeric comparison"); } else if (leftType.isNumber()) { validator.expectNumber( t, n, rightType, "right side of numeric comparison"); } else if (leftType.matchesNumberContext() && rightType.matchesNumberContext()) { // OK. } else { // Whether the comparison is numeric will be determined at runtime // each time the expression is evaluated. Regardless, both operands // should match a string context. String message = "left side of comparison

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>"; validator.expectString(t, n, leftType, message); validator.expectNotVoid( t, n, leftType, message, getNativeType(STRING_TYPE)); message = "right side of comparison"; validator.expectString(t, n, rightType, message); validator.expectNotVoid( t, n, rightType, message, getNativeType(STRING_TYPE)); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.IN: left = n.getFirstChild(); right = n.getLastChild(); leftType = getJSType(left); rightType = getJSType(right); validator.expectObject(t, n, rightType, "'in' requires an object"); validator.expectString(t, left, leftType, "left side of 'in'"); ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.INSTANCEOF: left = n.getFirstChild(); right = n.getLastChild(); leftType = getJSType(left); rightType = getJSType(right).restrictByNotNullOrUndefined(); validator.expectAnyObject( t, left, leftType, "deterministic instanceof yields false"); validator.expectActualObject( t, right, rightType, "instanceof requires an object"); ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.ASSIGN: visitAssign(t, n); typeable = false; break; case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_SUB: case Token.ASSIGN_ADD: case Token.ASSIGN_MUL: case Token.LSH: case Token.RSH: case Token.URSH: case Token.DIV: case Token.MOD: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.SUB: case Token.ADD: case Token.MUL: visitBinaryOperator(n.getType(), t, n); break; case Token.DELPROP: if (!isReference(n.getFirstChild())) { report(t, n, BAD_DELETE); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.CASE: JSType switchType = getJSType(parent.getFirstChild()); JSType caseType = getJSType(n.getFirstChild()); validator.expectSwitchMatchesCase(t, n, switchType, caseType); typeable = false; break; case Token.WITH: { Node child = n.getFirstChild(); childType = getJSType(child); validator.expectObject( t, child, childType, "with

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.LABEL_NAME: case Token.SWITCH: case Token.BREAK: case Token.CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.FOR: case Token.IF: case Token.WHILE: typeable = false; break; // These nodes are typed during the type inference. case Token.AND: case Token.HOOK: case Token.OBJECTLIT: case Token.OR: if (n.getJSType() != null) { // If we didn't run type inference. ensureTyped(t, n); } else { // If this is an enum, then give that type to the objectlit as well. if ((n.getType() == Token.OBJECTLIT) && (parent.getJSType() instanceof EnumType)) { ensureTyped(t, n, parent.getJSType()); } else { ensureTyped(t, n); } } break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType())); ensureTyped(t, n); break; } // Don't count externs since the user's code may not even use that part. typeable = typeable && !inExterns; if (typeable) { doPercentTypedAccounting(t, n); } checkNoTypeCheckSection(n, false); } /** * Counts the given node in the typed statistics. * @param n a node that should be typed */ private void doPercentTypedAccounting(NodeTraversal t, Node n) { JSType type = n.getJSType(); if (type == null) { nullCount++; } else if (type.isUnknownType()) { if (reportUnknownTypes.isOn()) { compiler.report( t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE)); } unknownCount++; } else { typedCount++; } } /** * Visits an assignment <code>lvalue = rvalue</code>. If the * <code>lvalue</code> is a prototype modification, we change the schema * of the object type it is referring to. * @param t the traversal * @param assign the assign node * (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant) */ private void visitAssign(NodeTraversal t, Node assign) { JSDoc

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Info info = assign.getJSDocInfo(); Node lvalue = assign.getFirstChild(); Node rvalue = assign.getLastChild(); if (lvalue.getType() == Token.GETPROP) { Node object = lvalue.getFirstChild(); JSType objectJsType = getJSType(object); String property = lvalue.getLastChild().getString(); // the first name in this getprop refers to an interface // we perform checks in addition to the ones below if (object.getType() == Token.GETPROP) { JSType jsType = getJSType(object.getFirstChild()); if (jsType.isInterface() && object.getLastChild().getString().equals("prototype")) { visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue); } } // /** @type ... */object.name = ...; if (info != null && info.hasType()) { visitAnnotatedAssignGetprop(t, assign, info.getType().evaluate(t.getScope(), typeRegistry), object, property, rvalue); return; } // /** @enum ... */object.name = ...; if (info != null && info.hasEnumParameterType()) { checkEnumInitializer( t, rvalue, info.getEnumParameterType().evaluate( t.getScope(), typeRegistry)); return; } // object.prototype = ...; if (property.equals("prototype")) { if (objectJsType instanceof FunctionType) { FunctionType functionType = (FunctionType) objectJsType; if (functionType.isConstructor()) { JSType rvalueType = rvalue.getJSType(); validator.expectObject(t, rvalue, rvalueType, OVERRIDING_PROTOTYPE_WITH_NON_OBJECT); } } else { // TODO(user): might want to flag that } return; } // object.prototype.property = ...; if (object.getType() == Token.GETPROP) { Node object2 = object.getFirstChild(); String property2 = NodeUtil.getStringValue(object.getLastChild()); if ("prototype".equals(property2)) { JSType jsType = object2.getJSType(); if (jsType instanceof FunctionType) { FunctionType functionType = (FunctionType) jsType; if (functionType.isConstructor() || functionType.isInterface()) { checkDeclaredPropertyInheritance( t, assign, functionType, property, info, getJSType(rvalue)); } } else { // TODO(user): might want to flag that } return; } } // object.property = ...; ObjectType type = ObjectType.cast( objectJsType.restrictByNotNullOrUndefined()); if (type != null) { if (type.hasProperty(property) && !type.isPropertyTypeInferred(property) && !propertyIsImplicitCast(type, property)) { validator.expectCanAssignToPropertyOf(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> t, assign, getJSType(rvalue), type.getPropertyType(property), object, property); } return; } } else if (lvalue.getType() == Token.NAME) { // variable with inferred type case JSType rvalueType = getJSType(assign.getLastChild()); Var var = t.getScope().getVar(lvalue.getString()); if (var != null) { if (var.isTypeInferred()) { return; } } } // fall through case JSType leftType = getJSType(lvalue); Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } /** * Returns true if any type in the chain has an implictCast annotation for * the given property. */ private boolean propertyIsImplicitCast(ObjectType type, String prop) { for (; type != null; type = type.getImplicitPrototype()) { JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop); if (docInfo != null && docInfo.isImplicitCast()) { return true; } } return false; } /** * Given a constructor type and a property name, check that the property has * the JSDoc annotation @override iff the property is declared on a * superclass. Several checks regarding inheritance correctness are also * performed. */ private void checkDeclaredPropertyInheritance( NodeTraversal t, Node n, FunctionType ctorType, String propertyName, JSDocInfo info, JSType propertyType) { // TODO(user): We're not 100% confident that type-checking works, // so we return quietly if the unknown type is a superclass of this type. // Remove this check as we become more confident. We should flag a warning // when the unknown type is on the inheritance chain, as it is likely // because of a programmer error. if (ctorType.hasUnknownSupertype()) { return; } FunctionType superClass = ctorType.getSuperClassConstructor(); boolean superClassHasProperty = superClass != null && superClass.getPrototype().hasProperty(propertyName); boolean declaredOverride = info != null && info.isOverride(); boolean foundInterfaceProperty = false; if (ctorType.isConstructor()) { for (JSType implementedInterface : ctorType.getImplementedInterfaces()) { if (implementedInterface.isUnknownType()) { continue; } FunctionType interfaceType = implementedInterface.toObjectType().getConstructor(); boolean interfaceHasProperty = interfaceType.getPrototype().hasProperty(propertyName); foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty; if (reportMissingOverride.isOn() && !declaredOverride && interfaceHasProperty) { // @override

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> bad i18n style but we don't localize our compiler errors. String abstractMethodMessage = (abstractMethodName != null) ? ", or " + abstractMethodName : ""; compiler.report( t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION, abstractMethodMessage)); } if (assign.getLastChild().getType() == Token.FUNCTION && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.report( t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits an ASSIGN node for cases such as * <pre> * object.property = ...; * </pre> * that have an {@code @type} annotation. */ private void visitAnnotatedAssignGetprop(NodeTraversal t, Node assign, JSType type, Node object, String property, Node rvalue) { // verifying that the rvalue has the correct type validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type, object, property); } /** * Visits a NAME node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. * @return whether the node is typeable or not */ boolean visitName(NodeTraversal t, Node n, Node parent) { // At this stage, we need to determine whether this is a leaf // node in an expression (which therefore needs to have a type // assigned for it) versus some other decorative node that we // can safely ignore. Function names, arguments (children of LP nodes) and // variable declarations are ignored. // TODO(user): remove this short-circuiting in favor of a // pre order traversal of the FUNCTION, CATCH, LP and VAR nodes. int parentNodeType = parent.getType(); if (parentNodeType == Token.FUNCTION || parentNodeType == Token.CATCH || parentNodeType == Token.LP || parentNodeType == Token.VAR) { return false; } JSType type = n.getJSType(); if (type == null) { type = getNativeType(UNKNOWN_TYPE); Var var = t.getScope().getVar(n.getString()); if (var != null) { JSType varType = var.getType(); if (varType != null) { type = varType; } } } ensureTyped(t, n, type); return true; } /** * Visits a GETPROP node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of <code>n</code

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>> */ private void visitGetProp(NodeTraversal t, Node n, Node parent) { // GETPROP nodes have an assigned type on their node by the scope creator // if this is an enum declaration. The only namespaced enum declarations // that we allow are of the form object.name = ...; if (n.getJSType() != null && parent.getType() == Token.ASSIGN) { return; } // obj.prop or obj.method() // Lots of types can appear on the left, a call to a void function can // never be on the left. getPropertyType will decide what is acceptable // and what isn't. Node property = n.getLastChild(); Node objNode = n.getFirstChild(); JSType childType = getJSType(objNode); // TODO(user): remove in favor of flagging every property access on // non-object. if (!validator.expectNotVoid(t, n, childType, "undefined has no properties", getNativeType(OBJECT_TYPE))) { ensureTyped(t, n); return; } checkPropertyAccess(childType, property.getString(), t, n); ensureTyped(t, n); } /** * Make sure that the access of this property is ok. */ private void checkPropertyAccess(JSType childType, String propName, NodeTraversal t, Node n) { ObjectType objectType = childType.dereference(); if (objectType != null) { JSType propType = getJSType(n); if ((!objectType.hasProperty(propName) || objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) && propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) { if (objectType instanceof EnumType) { report(t, n, INEXISTENT_ENUM_ELEMENT, propName); } else if (!objectType.isEmptyType() && reportMissingProperties && !isPropertyTest(n)) { if (!typeRegistry.canPropertyBeDefined(objectType, propName)) { report(t, n, INEXISTENT_PROPERTY, propName, validator.getReadableJSTypeName(n.getFirstChild(), true)); } } } } else { // TODO(nicksantos): might want to flag the access on a non object when // it's impossible to get a property from this type. } } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().getType() == Token.OR && parent.getParent().getFirstChild() == parent; } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { Node left = n.getFirstChild(); Node right = n.getLastChild(); validator.expectIndexMatch(t, n, getJSType(left), getJSType(right)); ensureTyped(t, n); } /** * Visits a VAR node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitVar(NodeTraversal t, Node n) { // TODO(nicksantos): Fix this so that the doc info always shows up // on the NAME node. We probably want to wait for the parser // merge to fix this. JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null; for (Node name : n.children()) { Node value = name.getFirstChild(); // A null var would indicate a bug in the scope creation logic. Var var = t.getScope().getVar(name.getString()); if (value != null) { JSType valueType = getJSType(value); JSType nameType = var.getType(); nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType; JSDocInfo info = name.getJSDocInfo(); if (info == null) { info = varInfo; } if (info != null && info.hasEnumParameterType()) { // var.getType() can never be null, this would indicate a bug in the // scope creation logic. checkEnumInitializer( t, value, info.getEnumParameterType().evaluate(t.getScope(), typeRegistry)); } else if (var.isTypeInferred()) { ensureTyped(t, name, valueType); } else { validator.expectCanAssignTo( t, value, valueType, nameType, "initializing variable"); } } } } /** * Visits a NEW node. */ private void visitNew(NodeTraversal t, Node n) { Node constructor = n.getFirstChild(); FunctionType type = getFunctionType(constructor); if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>type != null && type.isConstructor()) { visitParameterList(t, n, type); ensureTyped(t, n, type.getInstanceType()); } else { // TODO(user): add support for namespaced objects. if (constructor.getType() != Token.GETPROP) { // TODO(user): make the constructor node have lineno/charno // and use constructor for a more precise error indication. // It seems that GETPROP nodes are missing this information. Node line; if (constructor.getLineno() < 0 || constructor.getCharno() < 0) { line = n; } else { line = constructor; } report(t, line, NOT_A_CONSTRUCTOR); } ensureTyped(t, n); } } /** * Visits a {@link Token#FUNCTION} node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitFunction(NodeTraversal t, Node n) { JSDocInfo info = n.getJSDocInfo(); FunctionType functionType = (FunctionType) n.getJSType(); String functionPrivateName = n.getFirstChild().getString(); if (functionType.isInterface() || functionType.isConstructor()) { FunctionType baseConstructor = functionType. getPrototype().getImplicitPrototype().getConstructor(); if (baseConstructor != null && baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) && (baseConstructor.isConstructor() && functionType.isInterface() || baseConstructor.isInterface() && functionType.isConstructor())) { compiler.report( t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName)); } for (JSType baseInterface : functionType.getImplementedInterfaces()) { boolean badImplementedType = false; ObjectType baseInterfaceObj = ObjectType.cast(baseInterface); if (baseInterfaceObj != null) { FunctionType interfaceConstructor = baseInterfaceObj.getConstructor(); if (interfaceConstructor != null && !interfaceConstructor.isInterface()) { badImplementedType = true; } } else { badImplementedType = true; } if (badImplementedType) { report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName); } } if (functionType.isConstructor()) { validator.expectAllInterfacePropertiesImplemented(functionType); } } } /** * Visits a CALL node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitCall(NodeTraversal t, Node n) { Node child = n.getFirstChild(); JSType childType = getJSType(child).restrictByNotNullOr

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> will fail to compile, // so let it go. if (function == null) { return; } JSType jsType = getJSType(function); if (jsType instanceof FunctionType) { FunctionType functionType = (FunctionType) jsType; JSType returnType = functionType.getReturnType(); // if no return type is specified, undefined must be returned // (it's a void function) if (returnType == null) { returnType = getNativeType(VOID_TYPE); } // fetching the returned value's type Node valueNode = n.getFirstChild(); JSType actualReturnType; if (valueNode == null) { actualReturnType = getNativeType(VOID_TYPE); valueNode = n; } else { actualReturnType = getJSType(valueNode); } // verifying validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType, "inconsistent return type"); } } /** * This function unifies the type checking involved in the core binary * operators and the corresponding assignment operators. The representation * used internally is such that common code can handle both kinds of * operators easily. * * @param op The operator. * @param t The traversal object, needed to report errors. * @param n The node being checked. */ private void visitBinaryOperator(int op, NodeTraversal t, Node n) { Node left = n.getFirstChild(); JSType leftType = getJSType(left); Node right = n.getLastChild(); JSType rightType = getJSType(right); switch (op) { case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: if (!leftType.matchesInt32Context()) { report(t, left, BIT_OPERATION, NodeUtil.opToStr(n.getType()), leftType.toString()); } if (!rightType.matchesUint32Context()) { report(t, right, BIT_OPERATION, NodeUtil.opToStr(n.getType()), rightType.toString()); } break; case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right, rightType, "right operand"); break; case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITOR: case Token.BITAND: case Token.BITXOR: case Token.BITOR: validator.expectBitwiseable(t, left, leftType, "bad left operand to bitwise operator"); validator.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>expectBitwiseable(t, right, rightType, "bad right operand to bitwise operator"); break; case Token.ASSIGN_ADD: case Token.ADD: break; default: report(t, n, UNEXPECTED_TOKEN, Node.tokenToName(op)); } ensureTyped(t, n); } /** * <p>Checks the initializer of an enum. An enum can be initialized with an * object literal whose values must be subtypes of the declared enum element * type, or by copying another enum.</p> * * <p>In the case of an enum copy, we verify that the enum element type of the * enum used for initialization is a subtype of the enum element type of * the enum the value is being copied in.</p> * * <p>Examples:</p> * <pre>var myEnum = {FOO: ..., BAR: ...}; * var myEnum = myOtherEnum;</pre> * * @param value the value used for initialization of the enum * @param primitiveType The type of each element of the enum. */ private void checkEnumInitializer( NodeTraversal t, Node value, JSType primitiveType) { if (value.getType() == Token.OBJECTLIT) { for (Node key = value.getFirstChild(); key != null; key = key.getNext()) { Node propValue = key.getFirstChild(); // the value's type must be assignable to the enum's primitive type validator.expectCanAssignTo( t, propValue, getJSType(propValue), primitiveType, "element type must match enum's type"); } } else if (value.getJSType() instanceof EnumType) { // TODO(user): Remove the instanceof check in favor // of a type.isEnumType() predicate. Currently, not all enum types are // implemented by the EnumClass, e.g. the unknown type and the any // type. The types need to be defined by interfaces such that an // implementation can implement multiple types interface. EnumType valueEnumType = (EnumType) value.getJSType(); JSType valueEnumPrimitiveType = valueEnumType.getElementsType().getPrimitiveType(); validator.expectCanAssignTo(t, value, valueEnumPrimitiveType, primitiveType, "incompatible enum element types"); } else { // The error condition is handled in TypedScopeCreator. } } /** * This predicate is used to determine if the node represents an expression * that is a Reference according to JavaScript definitions. * * @param n The node being checked. * @return true if the sub-tree n is a reference, false otherwise. */ private static boolean isReference(Node n) { switch (n.getType()) { case Token.GETELEM: case Token.GETPROP: case Token.NAME: return true; default: return false; } } /** * This method gets the JSType from the Node argument

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> and verifies that it is * present. */ private JSType getJSType(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { // TODO(nicksantos): This branch indicates a compiler bug, not worthy of // halting the compilation but we should log this and analyze to track // down why it happens. This is not critical and will be resolved over // time as the type checker is extended. return getNativeType(UNKNOWN_TYPE); } else { return jsType; } } /** * Gets the type of the node or {@code null} if the node's type is not a * function. */ private FunctionType getFunctionType(Node n) { JSType type = getJSType(n).restrictByNotNullOrUndefined(); if (type.isUnknownType()) { return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE); } else if (type instanceof FunctionType) { return (FunctionType) type; } else { return null; } } // TODO(nicksantos): TypeCheck should never be attaching types to nodes. // All types should be attached by TypeInference. This is not true today // for legacy reasons. There are a number of places where TypeInference // doesn't attach a type, as a signal to TypeCheck that it needs to check // that node's type. /** * Ensure that the given node has a type. If it does not have one, * attach the UNKNOWN_TYPE. */ private void ensureTyped(NodeTraversal t, Node n) { ensureTyped(t, n, getNativeType(UNKNOWN_TYPE)); } private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) { ensureTyped(t, n, getNativeType(type)); } /** * Enforces type casts, and ensures the node is typed. * * A cast in the way that we use it in JSDoc annotations never * alters the generated code and therefore never can induce any runtime * operation. What this means is that a 'cast' is really just a compile * time constraint on the underlying value. In the future, we may add * support for run-time casts for compiled tests. * * To ensure some shred of sanity, we enforce the notion that the * type you are casting to may only meaningfully be a narrower type * than the underlying declared type. We also invalidate optimizations * on bad type casts. * * @param t The traversal object needed to report errors. * @param n The node getting a type assigned to it. * @param type The type to be assigned. */ private void ensureTyped(NodeTraversal t, Node n, JSType type) { // Make sure FUNCTION nodes always get function type. Preconditions.checkState(n.getType() != Token.FUNCTION || type instanceof FunctionType || type.isUnknownType());

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> JSDocInfo info = n.getJSDocInfo(); if (info != null) { if (info.hasType()) { JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry); validator.expectCanCast(t, n, infoType, type); type = infoType; } if (info.isImplicitCast() && !inExterns) { String propName = n.getType() == Token.GETPROP ? n.getLastChild().getString() : "(missing)"; compiler.report( t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName)); } } if (n.getJSType() == null) { n.setJSType(type); } } /** * Returns the percentage of nodes typed by the type checker. * @return a number between 0.0 and 100.0 */ double getTypedPercent() { int total = nullCount + unknownCount + typedCount; if (total == 0) { return 0.0; } else { return (100.0 * typedCount) / total; } } private JSType getNativeType(JSTypeNative typeId) { return typeRegistry.getNativeType(typeId); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> will * become the super node. */ public N getPartitionSuperNode(N node) { Preconditions.checkNotNull(colorToNodeMap, "No coloring founded. color() should be called first."); Color color = graph.getNode(node).getAnnotation(); N headNode = colorToNodeMap[color.value]; if (headNode == null) { colorToNodeMap[color.value] = node; return node; } else { return headNode; } } public AdjacencyGraph<N, E> getGraph() { return graph; } public static class Color implements Annotation { int value = 0; Color(int value) { this.value = value; } @Override public boolean equals(Object other) { if (!(other instanceof Color)) { return false; } else { return value == ((Color) other).value; } } @Override public int hashCode() { return value; } } /** * Greedily assign nodes with high degree unique colors. */ public static class GreedyGraphColoring<N, E> extends GraphColoring<N, E> { private final Comparator<N> tieBreaker; public GreedyGraphColoring(AdjacencyGraph<N, E> graph) { this(graph, null); } /** * @param tieBreaker In case of a tie between two nodes of the same degree, * this comparator will determine which node should be colored first. */ public GreedyGraphColoring( AdjacencyGraph<N, E> graph, Comparator<N> tieBreaker) { super(graph); this.tieBreaker = tieBreaker; } @Override public int color() { graph.clearNodeAnnotations(); List<GraphNode<N, E>> worklist = Lists.newArrayList(graph.getNodes()); // Sort nodes by degree. Collections.sort(worklist, new Comparator<GraphNode<N, E>>() { @Override public int compare(GraphNode<N, E> o1, GraphNode<N, E> o2) { int result = graph.getWeight(o2.getValue()) - graph.getWeight(o1.getValue()); return result == 0 && tieBreaker != null ? tieBreaker.compare(o1.getValue(), o2.getValue()) : result; } }); // Idea: From the highest to lowest degree, assign any uncolored node with // a unique color if none of its neighbor has been assigned that color. int count = 0; do { Color color = new Color(count); SubGraph<N, E> subgraph = graph.newSubGraph(); for (Iterator<GraphNode<N, E>> i = worklist.iterator(); i.hasNext();) { GraphNode<N, E> node = i.next(); if (subgraph.isIndependentOf(node.getValue())) { subgraph.addNode(node.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; /** * A factory for creating JSCompiler passes based on the Options * injected. Contains all meta-data about compiler passes (like * whether it can be run multiple times, a human-readable name for * logging, etc.). * * @author nicksantos@google.com (Nick Santos) */ public abstract class PassFactory { private final String name; private final boolean isOneTimePass; private boolean isCreated = false; /** * @param name The name of the pass that this factory creates. * @param isOneTimePass If true, the pass produced by this factory can * only be run once. */ protected PassFactory(String name, boolean isOneTimePass) { this.name = name; this.isOneTimePass = isOneTimePass; } /** * @return The name of this pass. */ String getName() { return name; } /** * @return Whether the pass produced by this factory can only be run once. */ boolean isOneTimePass() { return isOneTimePass; } /** * Make a new pass factory that only creates one-time passes. */ PassFactory makeOneTimePass() { if (isOneTimePass()) { return this; } final PassFactory self = this; return new PassFactory(name, true /* one time pass */) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return self.createInternal(compiler); } }; } /** * Creates a new compiler pass to be run. */ final CompilerPass create(AbstractCompiler compiler) { Preconditions.checkState(!isCreated || !isOneTimePass, "One-time passes cannot be run multiple times: " + name); isCreated = true; return createInternal(compiler); } /** * Creates a new compiler pass to be run. */ abstract protected CompilerPass createInternal(AbstractCompiler compiler); }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Node; /** * Look for references to the global RegExp object that would cause * regular expressions to be unoptimizable. * * @author johnlenz@google.com (John Lenz) */ class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType REGEXP_REFERENCE = DiagnosticType.warning("JSC_REGEXP_REFERENCE", "References to the global RegExp object prevents " + "optimization of regular expressions."); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL && first) || (parentType == Token.INSTANCEOF && !first))) { t.report(n, REGEXP_REFERENCE); globalRegExpPropertiesUsed = true; } } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.rhino.ast.Label; import com.google.javascript.jscomp.mozilla.rhino.ast.LabeledStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.Name; import com.google.javascript.jscomp.mozilla.rhino.ast.NewExpression; import com.google.javascript.jscomp.mozilla.rhino.ast.NumberLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectProperty; import com.google.javascript.jscomp.mozilla.rhino.ast.ParenthesizedExpression; import com.google.javascript.jscomp.mozilla.rhino.ast.PropertyGet; import com.google.javascript.jscomp.mozilla.rhino.ast.RegExpLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.ReturnStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.Scope; import com.google.javascript.jscomp.mozilla.rhino.ast.StringLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchCase; import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.ThrowStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.TryStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.UnaryExpression; import com.google.javascript.jscomp.mozilla.rhino.ast.VariableDeclaration; import com.google.javascript.jscomp.mozilla.rhino.ast.VariableInitializer; import com.google.javascript.jscomp.mozilla.rhino.ast.WhileLoop; import com.google.javascript.jscomp.mozilla.rhino.ast.WithStatement; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * IRFactory transforms the new AST to the old AST. * */ public class IRFactory { private final String sourceString; private final String sourceName; private final Config config; private final ErrorReporter errorReporter; private final TransformDispatcher transformDispatcher; // non-static for thread safety private final Set<String> ALLOWED_DIRECTIVES = Sets.newHashSet("use strict"); // @license text gets appended onto the fileLevelJsDocBuilder as found, // and stored in JSDocInfo for placeholder node. Node rootNodeJsDocHolder = new Node(Token.SCRIPT); Node.FileLevelJsDocBuilder fileLevelJsDocBuilder = rootNodeJsDocHolder.getJsDocBuilderForNode(); JSDocInfo fileOverviewInfo =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> null; // Use a template node for properties set on all nodes to minimize the // memory footprint associated with these. private Node templateNode; // TODO(johnlenz): Consider creating a template pool for ORIGINALNAME_PROP. private IRFactory(String sourceString, String sourceName, Config config, ErrorReporter errorReporter) { this.sourceString = sourceString; this.sourceName = sourceName; this.config = config; this.errorReporter = errorReporter; this.transformDispatcher = new TransformDispatcher(); // The template node properties are applied to all nodes in this transform. this.templateNode = createTemplateNode(); } // Create a template node to use as a source of common attributes, this allows // the prop structure to be shared among all the node from this source file. // This reduces the cost of these properties to O(nodes) to O(files). private Node createTemplateNode() { // The Node type choice is arbitrary. Node templateNode = new Node(Token.SCRIPT); templateNode.putProp(Node.SOURCENAME_PROP, sourceName); return templateNode; } public static Node transformTree(AstRoot node, String sourceString, Config config, ErrorReporter errorReporter) { IRFactory irFactory = new IRFactory(sourceString, node.getSourceName(), config, errorReporter); Node irNode = irFactory.transform(node); if (node.getComments() != null) { for (Comment comment : node.getComments()) { if (comment.getCommentType() == JSDOC && !comment.isParsed()) { irFactory.handlePossibleFileOverviewJsDoc(comment); } } } irFactory.setFileOverviewJsDoc(irNode); return irNode; } private void setFileOverviewJsDoc(Node irNode) { // Only after we've seen all @fileoverview entries, attach the // last one to the root node, and copy the found license strings // to that node. irNode.setJSDocInfo(rootNodeJsDocHolder.getJSDocInfo()); if (fileOverviewInfo != null) { if ((irNode.getJSDocInfo() != null) && (irNode.getJSDocInfo().getLicense() != null)) { fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense()); } irNode.setJSDocInfo(fileOverviewInfo); } } private Node transformBlock(AstNode node) { Node irNode = transform(node); if (irNode.getType() != Token.BLOCK) { if (irNode.getType() == Token.EMPTY) { irNode.setType(Token.BLOCK); irNode.setWasEmptyNode(true); } else { Node newBlock = newNode(Token.BLOCK, irNode); newBlock.setLineno(irNode.getLineno()); newBlock.setCharno(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>irNode.getCharno()); irNode = newBlock; } } return irNode; } /** * @return true if the jsDocParser represents a fileoverview. */ private boolean handlePossibleFileOverviewJsDoc( JsDocInfoParser jsDocParser) { if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) { fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo(); return true; } return false; } private void handlePossibleFileOverviewJsDoc(Comment comment) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment); comment.setParsed(true); handlePossibleFileOverviewJsDoc(jsDocParser); } private JSDocInfo handleJsDoc(AstNode node) { Comment comment = node.getJsDocNode(); if (comment != null) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment); comment.setParsed(true); if (!handlePossibleFileOverviewJsDoc(jsDocParser)) { return jsDocParser.retrieveAndResetParsedJSDocInfo(); } } return null; } private Node transform(AstNode node) { JSDocInfo jsDocInfo = handleJsDoc(node); Node irNode = justTransform(node); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } // If we have a named function, set the position to that of the name. if (irNode.getType() == Token.FUNCTION && irNode.getFirstChild().getLineno() != -1) { irNode.setLineno(irNode.getFirstChild().getLineno()); irNode.setCharno(irNode.getFirstChild().getCharno()); } else { if (irNode.getLineno() == -1) { // If we didn't already set the line, then set it now. This avoids // cases like ParenthesizedExpression where we just return a previous // node, but don't want the new node to get its parent's line number. int lineno = node.getLineno(); irNode.setLineno(lineno); int charno = position2charno(node.getAbsolutePosition()); irNode.setCharno(charno); } } return irNode; } /** * Creates a JsDocInfoParser and parses the JsDoc string. * * Used both for handling individual JSDoc comments and for handling * file-level JSDoc comments (@fileoverview and @license). * * @param node The JsDoc Comment node to parse. * @return A JSDocInfoParser. Will contain either fileoverview jsdoc, or * normal jsdoc, or no jsdoc (if the method parses to the wrong level). */ private JsDocInfoParser createJsDocInfoParser(Comment node) { String comment = node.getValue(); int

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> lineno = node.getLineno(); int position = node.getAbsolutePosition(); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser jsdocParser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, position2charno(position) + numOpeningChars), sourceName, config, errorReporter); jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder); jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo); jsdocParser.parse(); return jsdocParser; } private int position2charno(int position) { int lineIndex = sourceString.lastIndexOf('\n', position); if (lineIndex == -1) { return position; } else { // Subtract one for initial position being 0. return position - lineIndex - 1; } } private Node justTransform(AstNode node) { return transformDispatcher.process(node); } private class TransformDispatcher extends TypeSafeDispatcher<Node> { private Node processGeneric( com.google.javascript.jscomp.mozilla.rhino.Node n) { Node node = newNode(transformTokenType(n.getType())); for (com.google.javascript.jscomp.mozilla.rhino.Node child : n) { node.addChildToBack(transform((AstNode)child)); } return node; } /** * Transforms the given node and then sets its type to Token.STRING if it * was Token.NAME. If its type was already Token.STRING, then quotes it. * Used for properties, as the old AST uses String tokens, while the new one * uses Name tokens for unquoted strings. For example, in * var o = {'a' : 1, b: 2}; * the string 'a' is quoted, while the name b is turned into a string, but * unquoted. */ private Node transformAsString(AstNode n) { Node ret = transform(n); if (ret.getType() == Token.STRING) { ret.putBooleanProp(Node.QUOTED_PROP, true); } else if (ret.getType() == Token.NAME) { ret.setType(Token.STRING); } return ret; } @Override Node processArrayLiteral(ArrayLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.ARRAYLIT); int skipCount = 0; for (AstNode child : literalNode.getElements()) { Node c = transform(child); if (c.getType() == Token.EMPTY) { skipCount++; } node.addChildToBack(c); } if (skipCount > 0) { int[] skipIndexes = new int[skipCount]; int

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> i = 0; int j = 0; for (Node child : node.children()) { if (child.getType() == Token.EMPTY) { node.removeChild(child); skipIndexes[j] = i; j++; } i++; } node.putProp(Node.SKIP_INDEXES_PROP, skipIndexes); } return node; } @Override Node processAssignment(Assignment assignmentNode) { return processInfixExpression(assignmentNode); } @Override Node processAstRoot(AstRoot rootNode) { Node node = newNode(Token.SCRIPT); for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) { node.addChildToBack(transform((AstNode)child)); } parseDirectives(node); return node; } /** * Parse the directives, encode them in the AST, and remove their nodes. * * For information on ES5 directives, see section 14.1 of * Ecma-262, Edition 5. * * It would be nice if Rhino would eventually take care of this for * us, but right now their directive-processing is a one-off. */ private void parseDirectives(Node node) { // Remove all the directives, and encode them in the AST. Set<String> directives = null; while (isDirective(node.getFirstChild())) { String directive = node.removeFirstChild().getFirstChild().getString(); if (directives == null) { directives = Sets.newHashSet(directive); } else { directives.add(directive); } } if (directives != null) { node.setDirectives(directives); } } private boolean isDirective(Node n) { if (n == null) return false; int nType = n.getType(); return (nType == Token.EXPR_RESULT || nType == Token.EXPR_VOID) && n.getFirstChild().getType() == Token.STRING && ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString()); } @Override Node processBlock(Block blockNode) { return processGeneric(blockNode); } @Override Node processBreakStatement(BreakStatement statementNode) { Node node = newNode(Token.BREAK); if (statementNode.getBreakLabel() != null) { Node labelName = transform(statementNode.getBreakLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processCatchClause(CatchClause clauseNode) { AstNode catchVar = clauseNode.getVarName(); Node node = newNode(Token.CATCH, transform(catchVar)); if (clauseNode.getCatchCondition() != null) { errorReporter.error( "Catch clauses are not supported", sourceName, clauseNode.getC

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>atchCondition().getLineno(), "", 0); } node.addChildToBack(transformBlock(clauseNode.getBody())); return node; } @Override Node processConditionalExpression(ConditionalExpression exprNode) { return newNode( Token.HOOK, transform(exprNode.getTestExpression()), transform(exprNode.getTrueExpression()), transform(exprNode.getFalseExpression())); } @Override Node processContinueStatement(ContinueStatement statementNode) { Node node = newNode(Token.CONTINUE); if (statementNode.getLabel() != null) { Node labelName = transform(statementNode.getLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processDoLoop(DoLoop loopNode) { return newNode( Token.DO, transformBlock(loopNode.getBody()), transform(loopNode.getCondition())); } @Override Node processElementGet(ElementGet getNode) { return newNode( Token.GETELEM, transform(getNode.getTarget()), transform(getNode.getElement())); } @Override Node processEmptyExpression(EmptyExpression exprNode) { Node node = newNode(Token.EMPTY); return node; } @Override Node processExpressionStatement(ExpressionStatement statementNode) { Node node = newNode(transformTokenType(statementNode.getType())); node.addChildToBack(transform(statementNode.getExpression())); return node; } @Override Node processForInLoop(ForInLoop loopNode) { return newNode( Token.FOR, transform(loopNode.getIterator()), transform(loopNode.getIteratedObject()), transformBlock(loopNode.getBody())); } @Override Node processForLoop(ForLoop loopNode) { Node node = newNode( Token.FOR, transform(loopNode.getInitializer()), transform(loopNode.getCondition()), transform(loopNode.getIncrement())); node.addChildToBack(transformBlock(loopNode.getBody())); return node; } @Override Node processFunctionCall(FunctionCall callNode) { Node node = newNode(transformTokenType(callNode.getType()), transform(callNode.getTarget())); for (AstNode child : callNode.getArguments()) { node.addChildToBack(transform(child)); } int leftParamPos = callNode.getAbsolutePosition() + callNode.getLp(); node.setLineno(callNode.getLineno()); node.setCharno(position2charno(leftParamPos)); return node; } @Override Node processFunctionNode(FunctionNode functionNode) { Name name = functionNode.getFunctionName(); Boolean isUnnamedFunction = false; if (name == null) { name = new Name(); name.setIdentifier(""); isUnnamedFunction = true; } Node node = newNode(Token.FUNCTION); Node newName =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> transform(name); if (isUnnamedFunction) { // Old Rhino tagged the empty name node with the line number of the // declaration. newName.setLineno(functionNode.getLineno()); // TODO(bowdidge) Mark line number of paren correctly. // Same problem as below - the left paren might not be on the // same line as the function keyword. int lpColumn = functionNode.getAbsolutePosition() + functionNode.getLp(); newName.setCharno(position2charno(lpColumn)); } node.addChildToBack(newName); Node lp = newNode(Token.LP); // The left paren's complicated because it's not represented by an // AstNode, so there's nothing that has the actual line number that it // appeared on. We know the paren has to appear on the same line as the // function name (or else a semicolon will be inserted.) If there's no // function name, assume the paren was on the same line as the function. // TODO(bowdidge): Mark line number of paren correctly. Name fnName = functionNode.getFunctionName(); if (fnName != null) { lp.setLineno(fnName.getLineno()); } else { lp.setLineno(functionNode.getLineno()); } int lparenCharno = functionNode.getLp() + functionNode.getAbsolutePosition(); lp.setCharno(position2charno(lparenCharno)); for (AstNode param : functionNode.getParams()) { lp.addChildToBack(transform(param)); } node.addChildToBack(lp); Node bodyNode = transform(functionNode.getBody()); parseDirectives(bodyNode); node.addChildToBack(bodyNode); return node; } @Override Node processIfStatement(IfStatement statementNode) { Node node = newNode(Token.IF); node.addChildToBack(transform(statementNode.getCondition())); node.addChildToBack(transformBlock(statementNode.getThenPart())); if (statementNode.getElsePart() != null) { node.addChildToBack(transformBlock(statementNode.getElsePart())); } return node; } @Override Node processInfixExpression(InfixExpression exprNode) { Node n = newNode( transformTokenType(exprNode.getType()), transform(exprNode.getLeft()), transform(exprNode.getRight())); // Set the line number here so we can fine-tune it in ways transform // doesn't do. n.setLineno(exprNode.getLineno()); // Position in new ASTNode is to start of expression, but old-fashioned // line numbers from Node reference the operator token. Add the offset // to the operator to get the correct character number. n.setCharno(position2charno(exprNode.getAbsolutePosition() + exprNode.get

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>OperatorPosition())); return n; } @Override Node processKeywordLiteral(KeywordLiteral literalNode) { return newNode(transformTokenType(literalNode.getType())); } @Override Node processLabel(Label labelNode) { return newStringNode(Token.LABEL_NAME, labelNode.getName()); } @Override Node processLabeledStatement(LabeledStatement statementNode) { Node node = newNode(Token.LABEL); Node prev = null; Node cur = node; for (Label label : statementNode.getLabels()) { if (prev != null) { prev.addChildToBack(cur); } cur.addChildToBack(transform(label)); cur.setLineno(label.getLineno()); int clauseAbsolutePosition = position2charno(label.getAbsolutePosition()); cur.setCharno(clauseAbsolutePosition); prev = cur; cur = newNode(Token.LABEL); } prev.addChildToBack(transform(statementNode.getStatement())); return node; } @Override Node processName(Name nameNode) { return newStringNode(Token.NAME, nameNode.getIdentifier()); } @Override Node processNewExpression(NewExpression exprNode) { return processFunctionCall(exprNode); } @Override Node processNumberLiteral(NumberLiteral literalNode) { return newNumberNode(literalNode.getNumber()); } @Override Node processObjectLiteral(ObjectLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.OBJECTLIT); for (ObjectProperty el : literalNode.getElements()) { if (!config.acceptES5) { if (el.isGetter()) { reportGetter(el); continue; } else if (el.isSetter()) { reportSetter(el); continue; } } Node key = transformAsString(el.getLeft()); if (el.isGetter()) { key.setType(Token.GET); } else if (el.isSetter()) { key.setType(Token.SET); } key.addChildToFront(transform(el.getRight())); node.addChildToBack(key); } return node; } @Override Node processObjectProperty(ObjectProperty propertyNode) { return processInfixExpression(propertyNode); } @Override Node processParenthesizedExpression(ParenthesizedExpression exprNode) { Node node = transform(exprNode.getExpression()); node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE); return node; } @Override Node processPropertyGet(PropertyGet getNode) { return newNode( Token.GETPROP, transform(getNode.getTarget()), transformAsString(getNode.getProperty())); } @Override Node processRegExpLiteral(RegExpLiteral literalNode) { Node literalStringNode = newStringNode(literalNode.getValue()); // assume it's on the same

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> line. literalStringNode.setLineno(literalNode.getLineno()); Node node = newNode(Token.REGEXP, literalStringNode); String flags = literalNode.getFlags(); if (flags != null && !flags.isEmpty()) { Node flagsNode = newStringNode(flags); // Assume the flags are on the same line as the literal node. flagsNode.setLineno(literalNode.getLineno()); node.addChildToBack(flagsNode); } return node; } @Override Node processReturnStatement(ReturnStatement statementNode) { Node node = newNode(Token.RETURN); if (statementNode.getReturnValue() != null) { node.addChildToBack(transform(statementNode.getReturnValue())); } return node; } @Override Node processScope(Scope scopeNode) { return processGeneric(scopeNode); } @Override Node processStringLiteral(StringLiteral literalNode) { Node n = newStringNode(literalNode.getValue()); return n; } @Override Node processSwitchCase(SwitchCase caseNode) { Node node; if (caseNode.isDefault()) { node = newNode(Token.DEFAULT); } else { AstNode expr = caseNode.getExpression(); node = newNode(Token.CASE, transform(expr)); } Node block = newNode(Token.BLOCK); block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); block.setLineno(caseNode.getLineno()); block.setCharno(position2charno(caseNode.getAbsolutePosition())); if (caseNode.getStatements() != null) { for (AstNode child : caseNode.getStatements()) { block.addChildToBack(transform(child)); } } node.addChildToBack(block); return node; } @Override Node processSwitchStatement(SwitchStatement statementNode) { Node node = newNode(Token.SWITCH, transform(statementNode.getExpression())); for (AstNode child : statementNode.getCases()) { node.addChildToBack(transform(child)); } return node; } @Override Node processThrowStatement(ThrowStatement statementNode) { return newNode(Token.THROW, transform(statementNode.getExpression())); } @Override Node processTryStatement(TryStatement statementNode) { Node node = newNode(Token.TRY, transformBlock(statementNode.getTryBlock())); Node block = newNode(Token.BLOCK); boolean lineSet = false; for (CatchClause cc : statementNode.getCatchClauses()) { // Mark the enclosing block at the same line as the first catch // clause. if (lineSet == false) { block.setLineno(cc.getLineno()); lineSet = true; } block.addChildToBack(transform(cc)); } node.addChildToBack(block); AstNode finallyBlock = statementNode.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>getFinallyBlock(); if (finallyBlock != null) { node.addChildToBack(transformBlock(finallyBlock)); } // If we didn't set the line on the catch clause, then // we've got an empty catch clause. Set its line to be the same // as the finally block (to match Old Rhino's behavior.) if ((lineSet == false) && (finallyBlock != null)) { block.setLineno(finallyBlock.getLineno()); } return node; } @Override Node processUnaryExpression(UnaryExpression exprNode) { int type = transformTokenType(exprNode.getType()); Node operand = transform(exprNode.getOperand()); if (type == Token.NEG && operand.getType() == Token.NUMBER) { operand.setDouble(-operand.getDouble()); return operand; } else { Node node = newNode(type, operand); if (exprNode.isPostfix()) { node.putBooleanProp(Node.INCRDECR_PROP, true); } return node; } } @Override Node processVariableDeclaration(VariableDeclaration declarationNode) { Node node = newNode(Token.VAR); for (VariableInitializer child : declarationNode.getVariables()) { node.addChildToBack(transform(child)); } return node; } @Override Node processVariableInitializer(VariableInitializer initializerNode) { Node node = transform(initializerNode.getTarget()); if (initializerNode.getInitializer() != null) { node.addChildToBack(transform(initializerNode.getInitializer())); node.setLineno(node.getLineno()); } return node; } @Override Node processWhileLoop(WhileLoop loopNode) { return newNode( Token.WHILE, transform(loopNode.getCondition()), transformBlock(loopNode.getBody())); } @Override Node processWithStatement(WithStatement statementNode) { return newNode( Token.WITH, transform(statementNode.getExpression()), transformBlock(statementNode.getStatement())); } @Override Node processIllegalToken(AstNode node) { errorReporter.error( "Unsupported syntax: " + com.google.javascript.jscomp.mozilla.rhino.Token.typeToName( node.getType()), sourceName, node.getLineno(), "", 0); return newNode(Token.EMPTY); } void reportDestructuringAssign(AstNode node) { errorReporter.error( "destructuring assignment forbidden", sourceName, node.getLineno(), "", 0); } void reportGetter(AstNode node) { errorReporter.error( "getters are not supported in Internet Explorer", sourceName, node.getLineno(), "", 0); } void reportSetter(AstNode node) { errorReporter.error( "setters are not supported in Internet Explorer", sourceName, node.getLineno(), "", 0);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } } private static int transformTokenType(int token) { switch (token) { case com.google.javascript.jscomp.mozilla.rhino.Token.ERROR: return Token.ERROR; case com.google.javascript.jscomp.mozilla.rhino.Token.EOF: return Token.EOF; case com.google.javascript.jscomp.mozilla.rhino.Token.EOL: return Token.EOL; case com.google.javascript.jscomp.mozilla.rhino.Token.ENTERWITH: return Token.ENTERWITH; case com.google.javascript.jscomp.mozilla.rhino.Token.LEAVEWITH: return Token.LEAVEWITH; case com.google.javascript.jscomp.mozilla.rhino.Token.RETURN: return Token.RETURN; case com.google.javascript.jscomp.mozilla.rhino.Token.GOTO: return Token.GOTO; case com.google.javascript.jscomp.mozilla.rhino.Token.IFEQ: return Token.IFEQ; case com.google.javascript.jscomp.mozilla.rhino.Token.IFNE: return Token.IFNE; case com.google.javascript.jscomp.mozilla.rhino.Token.SETNAME: return Token.SETNAME; case com.google.javascript.jscomp.mozilla.rhino.Token.BITOR: return Token.BITOR; case com.google.javascript.jscomp.mozilla.rhino.Token.BITXOR: return Token.BITXOR; case com.google.javascript.jscomp.mozilla.rhino.Token.BITAND: return Token.BITAND; case com.google.javascript.jscomp.mozilla.rhino.Token.EQ: return Token.EQ; case com.google.javascript.jscomp.mozilla.rhino.Token.NE: return Token.NE; case com.google.javascript.jscomp.mozilla.rhino.Token.LT: return Token.LT; case com.google.javascript.jscomp.mozilla.rhino.Token.LE: return Token.LE; case com.google.javascript.jscomp.mozilla.rhino.Token.GT: return Token.GT; case com.google.javascript.jscomp.mozilla.rhino.Token.GE: return Token.GE; case com.google.javascript.jscomp.mozilla.rhino.Token.LSH: return Token.LSH; case com.google.javascript.jscomp.mozilla.rhino.Token.RSH: return Token.RSH; case com.google.javascript.jscomp.mozilla.rhino.Token.URSH: return Token.URSH; case com.google.javascript.jscomp.mozilla.rhino.Token.ADD: return Token.ADD; case com.google.javascript.jscomp.mozilla.rhino.Token.SUB: return Token.SUB; case

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> com.google.javascript.jscomp.mozilla.rhino.Token.MUL: return Token.MUL; case com.google.javascript.jscomp.mozilla.rhino.Token.DIV: return Token.DIV; case com.google.javascript.jscomp.mozilla.rhino.Token.MOD: return Token.MOD; case com.google.javascript.jscomp.mozilla.rhino.Token.NOT: return Token.NOT; case com.google.javascript.jscomp.mozilla.rhino.Token.BITNOT: return Token.BITNOT; case com.google.javascript.jscomp.mozilla.rhino.Token.POS: return Token.POS; case com.google.javascript.jscomp.mozilla.rhino.Token.NEG: return Token.NEG; case com.google.javascript.jscomp.mozilla.rhino.Token.NEW: return Token.NEW; case com.google.javascript.jscomp.mozilla.rhino.Token.DELPROP: return Token.DELPROP; case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOF: return Token.TYPEOF; case com.google.javascript.jscomp.mozilla.rhino.Token.GETPROP: return Token.GETPROP; case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP: return Token.SETPROP; case com.google.javascript.jscomp.mozilla.rhino.Token.GETELEM: return Token.GETELEM; case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM: return Token.SETELEM; case com.google.javascript.jscomp.mozilla.rhino.Token.CALL: return Token.CALL; case com.google.javascript.jscomp.mozilla.rhino.Token.NAME: return Token.NAME; case com.google.javascript.jscomp.mozilla.rhino.Token.NUMBER: return Token.NUMBER; case com.google.javascript.jscomp.mozilla.rhino.Token.STRING: return Token.STRING; case com.google.javascript.jscomp.mozilla.rhino.Token.NULL: return Token.NULL; case com.google.javascript.jscomp.mozilla.rhino.Token.THIS: return Token.THIS; case com.google.javascript.jscomp.mozilla.rhino.Token.FALSE: return Token.FALSE; case com.google.javascript.jscomp.mozilla.rhino.Token.TRUE: return Token.TRUE; case com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ: return Token.SHEQ; case com.google.javascript.jscomp.mozilla.rhino.Token.SHNE: return Token.SHNE; case com.google.javascript.jscomp.mozilla.rhino.Token.REGEXP: return Token.REGEXP

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; case com.google.javascript.jscomp.mozilla.rhino.Token.BINDNAME: return Token.BINDNAME; case com.google.javascript.jscomp.mozilla.rhino.Token.THROW: return Token.THROW; case com.google.javascript.jscomp.mozilla.rhino.Token.RETHROW: return Token.RETHROW; case com.google.javascript.jscomp.mozilla.rhino.Token.IN: return Token.IN; case com.google.javascript.jscomp.mozilla.rhino.Token.INSTANCEOF: return Token.INSTANCEOF; case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_LOAD: return Token.LOCAL_LOAD; case com.google.javascript.jscomp.mozilla.rhino.Token.GETVAR: return Token.GETVAR; case com.google.javascript.jscomp.mozilla.rhino.Token.SETVAR: return Token.SETVAR; case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH_SCOPE: return Token.CATCH_SCOPE; case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_INIT_KEYS: return Token.ENUM_INIT_KEYS; case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_INIT_VALUES: return Token.ENUM_INIT_VALUES; case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_NEXT: return Token.ENUM_NEXT; case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_ID: return Token.ENUM_ID; case com.google.javascript.jscomp.mozilla.rhino.Token.THISFN: return Token.THISFN; case com.google.javascript.jscomp.mozilla.rhino.Token.RETURN_RESULT: return Token.RETURN_RESULT; case com.google.javascript.jscomp.mozilla.rhino.Token.ARRAYLIT: return Token.ARRAYLIT; case com.google.javascript.jscomp.mozilla.rhino.Token.OBJECTLIT: return Token.OBJECTLIT; case com.google.javascript.jscomp.mozilla.rhino.Token.GET_REF: return Token.GET_REF; case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF: return Token.SET_REF; case com.google.javascript.jscomp.mozilla.rhino.Token.DEL_REF: return Token.DEL_REF; case com.google.javascript.jscomp.mozilla.rhino.Token.REF_CALL: return Token.REF_CALL; case com.google.javascript.jscomp.mozilla.rhino.Token.REF_SPECIAL: return Token.REF_SPECIAL; case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULTNAMESPACE: return Token

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.DEFAULTNAMESPACE; case com.google.javascript.jscomp.mozilla.rhino.Token.ESCXMLTEXT: return Token.ESCXMLTEXT; case com.google.javascript.jscomp.mozilla.rhino.Token.ESCXMLATTR: return Token.ESCXMLATTR; case com.google.javascript.jscomp.mozilla.rhino.Token.REF_MEMBER: return Token.REF_MEMBER; case com.google.javascript.jscomp.mozilla.rhino.Token.REF_NS_MEMBER: return Token.REF_NS_MEMBER; case com.google.javascript.jscomp.mozilla.rhino.Token.REF_NAME: return Token.REF_NAME; case com.google.javascript.jscomp.mozilla.rhino.Token.REF_NS_NAME: return Token.REF_NS_NAME; case com.google.javascript.jscomp.mozilla.rhino.Token.TRY: return Token.TRY; case com.google.javascript.jscomp.mozilla.rhino.Token.SEMI: return Token.SEMI; case com.google.javascript.jscomp.mozilla.rhino.Token.LB: return Token.LB; case com.google.javascript.jscomp.mozilla.rhino.Token.RB: return Token.RB; case com.google.javascript.jscomp.mozilla.rhino.Token.LC: return Token.LC; case com.google.javascript.jscomp.mozilla.rhino.Token.RC: return Token.RC; case com.google.javascript.jscomp.mozilla.rhino.Token.LP: return Token.LP; case com.google.javascript.jscomp.mozilla.rhino.Token.RP: return Token.RP; case com.google.javascript.jscomp.mozilla.rhino.Token.COMMA: return Token.COMMA; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN: return Token.ASSIGN; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITOR: return Token.ASSIGN_BITOR; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITXOR: return Token.ASSIGN_BITXOR; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITAND: return Token.ASSIGN_BITAND; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_LSH: return Token.ASSIGN_LSH; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_RSH: return Token.ASSIGN_RSH; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_URSH: return Token.ASSIGN_URSH; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_ADD:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return Token.ASSIGN_ADD; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_SUB: return Token.ASSIGN_SUB; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL: return Token.ASSIGN_MUL; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV: return Token.ASSIGN_DIV; case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD: return Token.ASSIGN_MOD; case com.google.javascript.jscomp.mozilla.rhino.Token.HOOK: return Token.HOOK; case com.google.javascript.jscomp.mozilla.rhino.Token.COLON: return Token.COLON; case com.google.javascript.jscomp.mozilla.rhino.Token.OR: return Token.OR; case com.google.javascript.jscomp.mozilla.rhino.Token.AND: return Token.AND; case com.google.javascript.jscomp.mozilla.rhino.Token.INC: return Token.INC; case com.google.javascript.jscomp.mozilla.rhino.Token.DEC: return Token.DEC; case com.google.javascript.jscomp.mozilla.rhino.Token.DOT: return Token.DOT; case com.google.javascript.jscomp.mozilla.rhino.Token.FUNCTION: return Token.FUNCTION; case com.google.javascript.jscomp.mozilla.rhino.Token.EXPORT: return Token.EXPORT; case com.google.javascript.jscomp.mozilla.rhino.Token.IMPORT: return Token.IMPORT; case com.google.javascript.jscomp.mozilla.rhino.Token.IF: return Token.IF; case com.google.javascript.jscomp.mozilla.rhino.Token.ELSE: return Token.ELSE; case com.google.javascript.jscomp.mozilla.rhino.Token.SWITCH: return Token.SWITCH; case com.google.javascript.jscomp.mozilla.rhino.Token.CASE: return Token.CASE; case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULT: return Token.DEFAULT; case com.google.javascript.jscomp.mozilla.rhino.Token.WHILE: return Token.WHILE; case com.google.javascript.jscomp.mozilla.rhino.Token.DO: return Token.DO; case com.google.javascript.jscomp.mozilla.rhino.Token.FOR: return Token.FOR; case com.google.javascript.jscomp.mozilla.rhino.Token.BREAK: return Token.BREAK; case com.google.javascript.jscomp.mozilla.rhino.Token.CONTINUE: return Token.CONTINUE; case com.google.javascript.jscomp.mozilla.rhino.Token.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>VAR: return Token.VAR; case com.google.javascript.jscomp.mozilla.rhino.Token.WITH: return Token.WITH; case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH: return Token.CATCH; case com.google.javascript.jscomp.mozilla.rhino.Token.FINALLY: return Token.FINALLY; case com.google.javascript.jscomp.mozilla.rhino.Token.VOID: return Token.VOID; case com.google.javascript.jscomp.mozilla.rhino.Token.RESERVED: return Token.RESERVED; case com.google.javascript.jscomp.mozilla.rhino.Token.EMPTY: return Token.EMPTY; case com.google.javascript.jscomp.mozilla.rhino.Token.BLOCK: return Token.BLOCK; case com.google.javascript.jscomp.mozilla.rhino.Token.LABEL: return Token.LABEL; case com.google.javascript.jscomp.mozilla.rhino.Token.TARGET: return Token.TARGET; case com.google.javascript.jscomp.mozilla.rhino.Token.LOOP: return Token.LOOP; case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_VOID: case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_RESULT: return Token.EXPR_RESULT; case com.google.javascript.jscomp.mozilla.rhino.Token.JSR: return Token.JSR; case com.google.javascript.jscomp.mozilla.rhino.Token.SCRIPT: return Token.SCRIPT; case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOFNAME: return Token.TYPEOFNAME; case com.google.javascript.jscomp.mozilla.rhino.Token.USE_STACK: return Token.USE_STACK; case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP_OP: return Token.SETPROP_OP; case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM_OP: return Token.SETELEM_OP; case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_BLOCK: return Token.LOCAL_BLOCK; case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF_OP: return Token.SET_REF_OP; case com.google.javascript.jscomp.mozilla.rhino.Token.DOTDOT: return Token.DOTDOT; case com.google.javascript.jscomp.mozilla.rhino.Token.COLONCOLON: return Token.COLONCOLON; case com.google.javascript.jscomp.mozilla.rhino.Token.XML: return Token.XML; case com.google.javascript.jscomp.mozilla.rhino.Token

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.DOTQUERY: return Token.DOTQUERY; case com.google.javascript.jscomp.mozilla.rhino.Token.XMLATTR: return Token.XMLATTR; case com.google.javascript.jscomp.mozilla.rhino.Token.XMLEND: return Token.XMLEND; case com.google.javascript.jscomp.mozilla.rhino.Token.TO_OBJECT: return Token.TO_OBJECT; case com.google.javascript.jscomp.mozilla.rhino.Token.TO_DOUBLE: return Token.TO_DOUBLE; case com.google.javascript.jscomp.mozilla.rhino.Token.GET: return Token.GET; case com.google.javascript.jscomp.mozilla.rhino.Token.SET: return Token.SET; case com.google.javascript.jscomp.mozilla.rhino.Token.CONST: return Token.CONST; case com.google.javascript.jscomp.mozilla.rhino.Token.SETCONST: return Token.SETCONST; case com.google.javascript.jscomp.mozilla.rhino.Token.DEBUGGER: return Token.DEBUGGER; } // Token without name throw new IllegalStateException(String.valueOf(token)); } // Simple helper to create nodes and set the initial node properties. private Node newNode(int type) { return new Node(type).clonePropsFrom(templateNode); } private Node newNode(int type, Node child1) { return new Node(type, child1).clonePropsFrom(templateNode); } private Node newNode(int type, Node child1, Node child2) { return new Node(type, child1, child2).clonePropsFrom(templateNode); } private Node newNode(int type, Node child1, Node child2, Node child3) { return new Node(type, child1, child2, child3).clonePropsFrom(templateNode); } private Node newStringNode(String value) { return Node.newString(value).clonePropsFrom(templateNode); } private Node newStringNode(int type, String value) { return Node.newString(type, value).clonePropsFrom(templateNode); } private Node newNumberNode(Double value) { return Node.newNumber(value).clonePropsFrom(templateNode); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2007 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import static com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt.LINE; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.SourceExcerptProvider.ExcerptFormatter; import com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt; /** * Lightweight message formatter. The format of messages this formatter * produces is very compact and to the point. * */ public class LightweightMessageFormatter extends AbstractMessageFormatter { private SourceExcerpt excerpt; private static final ExcerptFormatter excerptFormatter = new LineNumberingFormatter(); /** * A constructor for when the client doesn't care about source information. */ private LightweightMessageFormatter() { super(null); this.excerpt = LINE; } public LightweightMessageFormatter(SourceExcerptProvider source) { this(source, LINE); } public LightweightMessageFormatter(SourceExcerptProvider source, SourceExcerpt excerpt) { super(source); Preconditions.checkNotNull(source); this.excerpt = excerpt; } static LightweightMessageFormatter withoutSource() { return new LightweightMessageFormatter(); } public String formatError(JSError error) { return format(error, false); } public String formatWarning(JSError warning) { return format(warning, true); } private String format(JSError error, boolean warning) { // extract source excerpt SourceExcerptProvider source = getSource(); String sourceExcerpt = source == null ? null : excerpt.get( source, error.sourceName, error.lineNumber, excerptFormatter); // formatting the message StringBuilder b = new StringBuilder(); if (error.sourceName != null) { b.append(error.sourceName); if (error.lineNumber > 0) { b.append(':'); b.append(error.lineNumber); } b.append(": "); } b.append(getLevelName(warning ? CheckLevel.WARNING : CheckLevel.ERROR)); b.append(" - "); b.append(error.description); b.append('\n'); if (sourceEx

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> input, Set<INPUT> subGraph) { for (String symbol : input.getRequires()) { INPUT candidate = provideMap.get(symbol); if (subGraph.contains(candidate)) { return candidate; } } throw new IllegalStateException("no require found in subgraph"); } /** * @param cycle A cycle in reverse-dependency order. */ private String cycleToString(List<INPUT> cycle) { List<String> symbols = Lists.newArrayList(); for (int i = cycle.size() - 1; i >= 0; i--) { symbols.add(cycle.get(i).getProvides().iterator().next()); } symbols.add(symbols.get(0)); return Joiner.on(" -> ").join(symbols); } public List<INPUT> getSortedList() { return Collections.<INPUT>unmodifiableList(sortedList); } /** * Gets all the dependencies of the given roots. The inputs must be returned * in a stable order. In other words, if A comes before B, and A does not * transitively depend on B, then A must also come before B in the returned * list. */ public List<INPUT> getSortedDependenciesOf(List<INPUT> roots) { Preconditions.checkArgument(inputs.containsAll(roots)); Set<INPUT> included = Sets.newHashSet(); Deque<INPUT> worklist = new ArrayDeque<INPUT>(roots); while (!worklist.isEmpty()) { INPUT current = worklist.pop(); if (included.add(current)) { for (String req : current.getRequires()) { INPUT dep = provideMap.get(req); if (dep != null) { worklist.add(dep); } } } } ImmutableList.Builder<INPUT> builder = ImmutableList.builder(); for (INPUT current : sortedList) { if (included.contains(current)) { builder.add(current); } } return builder.build(); } public List<INPUT> getInputsWithoutProvides() { return Collections.<INPUT>unmodifiableList(noProvides); } private static <T> List<T> topologicalStableSort( List<T> items, Multimap<T, T> deps) { final Map<T, Integer> originalIndex = Maps.newHashMap(); for (int i = 0; i < items.size(); i++) { originalIndex.put(items.get(i), i); } PriorityQueue<T> inDegreeZero = new PriorityQueue<T>(items.size(), new Comparator<T>() { @Override public int compare(T a, T b) { return originalIndex.get(a).intValue() - originalIndex.get(b).intValue(); } }); List<T> result = Lists.newArrayList(); Multiset<T> inDegree = HashMultiset.create(); Multimap<T, T> reverseDeps = ArrayListMultimap.create();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(); if (grandparent.getType() == Token.NAME && grandparent.getString() == v.name) { continue; } // Only generate warnings if the scopes do not match in order // to deal with possible forward declarations and recursion if (reference.getScope() == v.scope) { compiler.report( JSError.make(reference.getSourceName(), reference.getNameNode(), checkLevel, UNDECLARED_REFERENCE, v.name)); } } if (isDeclaration) { blocksWithDeclarations.add(basicBlock); isDeclaredInScope = true; } } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { return "{...}"; } } void setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; } @Override public FunctionType getConstructor() { return null; } @Override public ObjectType getImplicitPrototype() { return implicitPrototypeFallback; } /** * This should only be reset on the FunctionPrototypeType, only to fix an * incorrectly established prototype chain due to the user having a mismatch * in super class declaration, and only before properties on that type are * processed. */ final void setImplicitPrototype(ObjectType implicitPrototype) { checkState(!hasCachedValues()); this.implicitPrototypeFallback = implicitPrototype; } @Override public String getReferenceName() { if (className != null) { return className; } else { return null; } } @Override public boolean hasReferenceName() { return className != null; } @Override public boolean isSubtype(JSType that) { if (JSType.isSubtype(this, that)) { return true; } // Union types if (that instanceof UnionType) { // The static {@code JSType.isSubtype} check already decomposed // union types, so we don't need to check those again. return false; } // record types if (that instanceof RecordType) { return RecordType.isSubtype(this, (RecordType) that); } // Interfaces // Find all the interfaces implemented by this class and compare each one // to the interface instance. ObjectType thatObj = that.toObjectType(); ObjectType thatCtor = thatObj == null ? null : thatObj.getConstructor(); if (thatCtor != null && thatCtor.isInterface()) { Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces(); for (ObjectType thisInterface : thisInterfaces) { if (thisInterface.isSubtype(that)) { return true; } } } // other prototype based objects if (that != null) { if (isUnknownType() || implicitPrototypeChainIsUnknown()) { // If unsure, say 'yes', to avoid spurious warnings. // TODO(user): resolve the prototype chain completely in all cases, // to avoid guessing. return true; } return this.isImplicitPrototype(thatObj); } return false; } private boolean implicitPrototypeChainIsUnknown() { ObjectType p = getImplicitPrototype(); while (p != null) { if (p.isUnknownType()) { return true; } p = p.getImplicitPrototype(); } return false; } private static final class Property implements Serializable { private static final long serialVersionUID = 1L; /** * Property's type. */ private JSType type; /** * Whether the property's type is inferred. */ private final boolean inferred; /** * Whether the property is defined in the externs. */ private final boolean inExterns; /**

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Type.error("JSC_OPTIMIZE_LOOP_ERROR", "Exceeded max number of code motion iterations: {0}"); private static final long COMPILER_STACK_SIZE = 1048576L; /** * Logger for the whole com.google.javascript.jscomp domain - * setting configuration for this logger affects all loggers * in other classes within the compiler. */ private static final Logger logger = Logger.getLogger("com.google.javascript.jscomp"); private final PrintStream outStream; /** * Creates a Compiler that reports errors and warnings to its logger. */ public Compiler() { this((PrintStream) null); } /** * Creates n Compiler that reports errors and warnings to an output * stream. */ public Compiler(PrintStream stream) { addChangeHandler(recentChange); outStream = stream; } /** * Creates a Compiler that uses a custom error manager. */ public Compiler(ErrorManager errorManager) { this(); setErrorManager(errorManager); } /** * Sets the error manager. * * @param errorManager the error manager, it cannot be {@code null} */ public void setErrorManager(ErrorManager errorManager) { Preconditions.checkNotNull( errorManager, "the error manager cannot be null"); this.errorManager = errorManager; } /** * Creates a message formatter instance corresponding to the value of * {@link CompilerOptions}. */ private MessageFormatter createMessageFormatter() { boolean colorize = options.shouldColorizeErrorOutput(); return options.errorFormat.toFormatter(this, colorize); } /** * Initialize the compiler options. Only necessary if you're not doing * a normal compile() job. */ public void initOptions(CompilerOptions options) { this.options = options; if (errorManager == null) { if (outStream == null) { setErrorManager( new LoggerErrorManager(createMessageFormatter(), logger)); } else { PrintStreamErrorManager printer = new PrintStreamErrorManager(createMessageFormatter(), outStream); printer.setSummaryDetailLevel(options.summaryDetailLevel); setErrorManager(printer); } } // Initialize the warnings guard. List<WarningsGuard> guards = Lists.newArrayList(); guards.add( new SuppressDocWarningsGuard( getDiagnosticGroups().getRegisteredGroups())); WarningsGuard warningsGuard = options.getWarningsGuard(); if (warningsGuard != null) { guards.add(options.getWarningsGuard()); } // All passes must run the variable check. This synthesizes // variables later so that the compiler doesn't crash. It also // checks the externs file for validity. If you don't want to warn // about missing variable declarations, we shut that specific // error off. if (!options.checkSymbols && (warningsGuard == null || !warningsGuard.disables( DiagnosticGroups.CHECK_VARIABLES))) { gu

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>}"); /** * Creates a map to make looking up an input by name fast. Also checks for * duplicate inputs. */ void initInputsByNameMap() { inputsByName = new HashMap<String, CompilerInput>(); for (CompilerInput input : externs) { String name = input.getName(); if (!inputsByName.containsKey(name)) { inputsByName.put(name, input); } else { report(JSError.make(DUPLICATE_EXTERN_INPUT, name)); } } for (CompilerInput input : inputs) { String name = input.getName(); if (!inputsByName.containsKey(name)) { inputsByName.put(name, input); } else { report(JSError.make(DUPLICATE_INPUT, name)); } } } public Result compile( JSSourceFile extern, JSSourceFile input, CompilerOptions options) { return compile(extern, new JSSourceFile[] { input }, options); } public Result compile( JSSourceFile extern, JSSourceFile[] input, CompilerOptions options) { return compile(new JSSourceFile[] { extern }, input, options); } public Result compile( JSSourceFile extern, JSModule[] modules, CompilerOptions options) { return compile(new JSSourceFile[] { extern }, modules, options); } /** * Compiles a list of inputs. */ public Result compile(JSSourceFile[] externs, JSSourceFile[] inputs, CompilerOptions options) { return compile(Lists.<JSSourceFile>newArrayList(externs), Lists.<JSSourceFile>newArrayList(inputs), options); } /** * Compiles a list of inputs. */ public Result compile(List<JSSourceFile> externs, List<JSSourceFile> inputs, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { init(externs, inputs, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } /** * Compiles a list of modules. */ public Result compile(JSSourceFile[] externs, JSModule[] modules, CompilerOptions options) { return compileModules(Lists.<JSSourceFile>newArrayList(externs), Lists.<JSModule>newArrayList(modules), options); } /** * Compiles a list of modules. */ public Result compileModules(List<JSSourceFile> externs, List<JSModule> modules, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { initModules(externs, modules, options); if (hasErrors()) { return getResult();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ExternExportsEnabled() || options.externExportsPath != null) { externExports(); } // IDE-mode is defined to stop here, before the heavy rewriting begins. if (!options.ideMode) { optimize(); } } if (options.recordFunctionInformation) { recordFunctionInformation(); } if (options.devMode == DevMode.START_AND_END) { runSanityCheck(); } } public void parse() { parseInputs(); } PassConfig getPassConfig() { if (passes == null) { passes = createPassConfigInternal(); } return passes; } /** * Create the passes object. Clients should use setPassConfig instead of * overriding this. */ PassConfig createPassConfigInternal() { return new DefaultPassConfig(options); } /** * @param passes The PassConfig to use with this Compiler. * @throws NullPointerException if passes is null * @throws IllegalStateException if this.passes has already been assigned */ public void setPassConfig(PassConfig passes) { // Important to check for null because if setPassConfig(null) is // called before this.passes is set, getPassConfig() will create a // new PassConfig object and use that, which is probably not what // the client wanted since he or she probably meant to use their // own PassConfig object. Preconditions.checkNotNull(passes); if (this.passes != null) { throw new IllegalStateException("this.passes has already been assigned"); } this.passes = passes; } /** * Carry out any special checks or procedures that need to be done before * proceeding with rest of the compilation process. * * @return true, to continue with compilation */ boolean precheck() { return true; } public void check() { runCustomPasses(CustomPassExecutionTime.BEFORE_CHECKS); PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker); if (options.devMode == DevMode.EVERY_PASS) { phaseOptimizer.setSanityCheck(sanityCheck); } phaseOptimizer.consume(getPassConfig().getChecks()); phaseOptimizer.process(externsRoot, jsRoot); if (hasErrors()) { return; } // TODO(nicksantos): clean this up. The flow here is too hard to follow. if (options.nameAnonymousFunctionsOnly) { return; } if (options.removeTryCatchFinally) { removeTryCatchFinally(); } if (!options.stripTypes.isEmpty() || !options.stripNameSuffixes.isEmpty() || !options.stripTypePrefixes.isEmpty() || !options.stripNamePrefixes.isEmpty()) { stripCode(options.stripTypes, options.stripNameSuffixes, options.stripTypePrefixes, options.stripNamePrefixes); } runCustomPasses(CustomPassExecutionTime.BEFORE_OPTIMIZATIONS); } private void externExports

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>() { logger.info("Creating extern file for exports"); startPass("externExports"); ExternExportsPass pass = new ExternExportsPass(this); process(pass); externExports = pass.getGeneratedExterns(); endPass(); } void process(CompilerPass p) { p.process(externsRoot, jsRoot); } private final PassFactory sanityCheck = new PassFactory("sanityCheck", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new SanityCheck(compiler); } }; private void maybeSanityCheck() { if (options.devMode == DevMode.EVERY_PASS) { runSanityCheck(); } } private void runSanityCheck() { sanityCheck.create(this).process(externsRoot, jsRoot); } /** * Removes try/catch/finally statements for easier debugging. */ void removeTryCatchFinally() { logger.info("Remove try/catch/finally"); startPass("removeTryCatchFinally"); RemoveTryCatch r = new RemoveTryCatch(this); process(r); endPass(); } /** * Strips code for smaller compiled code. This is useful for removing debug * statements to prevent leaking them publicly. */ void stripCode(Set<String> stripTypes, Set<String> stripNameSuffixes, Set<String> stripTypePrefixes, Set<String> stripNamePrefixes) { logger.info("Strip code"); startPass("stripCode"); StripCode r = new StripCode(this, stripTypes, stripNameSuffixes, stripTypePrefixes, stripNamePrefixes); process(r); endPass(); } /** * Runs custom passes that are designated to run at a particular time. */ private void runCustomPasses(CustomPassExecutionTime executionTime) { if (options.customPasses != null) { Tracer t = newTracer("runCustomPasses"); try { for (CompilerPass p : options.customPasses.get(executionTime)) { process(p); } } finally { stopTracer(t, "runCustomPasses"); } } } private Tracer currentTracer = null; private String currentPassName = null; /** * Marks the beginning of a pass. */ void startPass(String passName) { Preconditions.checkState(currentTracer == null); currentPassName = passName; currentTracer = newTracer(passName); } /** * Marks the end of a pass. */ void endPass() { Preconditions.checkState(currentTracer != null, "Tracer should not be null at the end of a pass."); stopTracer(currentTracer, currentPassName); String passToCheck = currentPassName; currentPassName = null; currentTracer = null; maybeSanityCheck(); } /** * Returns a new tracer for the given pass

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } else { return n1.checkTreeEqualsSilent(n2); } } //------------------------------------------------------------------------ // Inputs //------------------------------------------------------------------------ // TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler // interface, and which ones should always be injected. @Override public CompilerInput getInput(String name) { return inputsByName.get(name); } @Override public CompilerInput newExternInput(String name) { if (inputsByName.containsKey(name)) { throw new IllegalArgumentException("Conflicting externs name: " + name); } SourceAst ast = new SyntheticAst(name); CompilerInput input = new CompilerInput(ast, name, true); inputsByName.put(name, input); externsRoot.addChildToFront(ast.getAstRoot(this)); return input; } /** Add a source input dynamically. Intended for incremental compilation. */ void addIncrementalSourceAst(JsAst ast) { String sourceName = ast.getSourceFile().getName(); Preconditions.checkState( getInput(sourceName) == null, "Duplicate input of name " + sourceName); inputsByName.put(sourceName, new CompilerInput(ast)); } /** * Replace a source input dynamically. Intended for incremental * re-compilation. * * If the new source input doesn't parse, then keep the old input * in the AST and return false. * * @return Whether the new AST was attached successfully. */ boolean replaceIncrementalSourceAst(JsAst ast) { String sourceName = ast.getSourceFile().getName(); CompilerInput oldInput = Preconditions.checkNotNull( getInput(sourceName), "No input to replace: " + sourceName); Node newRoot = ast.getAstRoot(this); if (newRoot == null) { return false; } Node oldRoot = oldInput.getAstRoot(this); if (oldRoot != null) { oldRoot.getParent().replaceChild(oldRoot, newRoot); } else { getRoot().getLastChild().addChildToBack(newRoot); } inputsByName.put(sourceName, new CompilerInput(ast)); return true; } @Override JSModuleGraph getModuleGraph() { return moduleGraph; } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes); } return typeRegistry; } @Override ScopeCreator getScopeCreator() { return getPassConfig().getScopeCreator(); } @Override public Scope getTopScope() { return getPassConfig().getTopScope(); } @Override public ReverseAbstractInterpreter getReverseAbstractInterpreter() { if (abstractInterpreter == null) { ChainableReverseAbstractInterpreter interpreter = new SemanticReverseAbstractInterpreter( getCodingConvention(), getTypeRegistry()); if (options.closurePass) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> interpreter = new ClosureReverseAbstractInterpreter( getCodingConvention(), getTypeRegistry()) .append(interpreter).getFirst(); } abstractInterpreter = interpreter; } return abstractInterpreter; } @Override TypeValidator getTypeValidator() { if (typeValidator == null) { typeValidator = new TypeValidator(this); } return typeValidator; } //------------------------------------------------------------------------ // Parsing //------------------------------------------------------------------------ /** * Parses the externs and main inputs. * * @return A synthetic root node whose two children are the externs root * and the main root */ Node parseInputs() { boolean devMode = options.devMode != DevMode.OFF; // If old roots exist (we are parsing a second time), detach each of the // individual file parse trees. if (externsRoot != null) { externsRoot.detachChildren(); } if (jsRoot != null) { jsRoot.detachChildren(); } // Parse main js sources. jsRoot = new Node(Token.BLOCK); jsRoot.setIsSyntheticBlock(true); if (options.tracer.isOn()) { tracker = new PerformanceTracker(jsRoot, options.tracer == TracerMode.ALL); addChangeHandler(tracker.getCodeChangeHandler()); } Tracer tracer = newTracer("parseInputs"); try { // Parse externs sources. externsRoot = new Node(Token.BLOCK); externsRoot.setIsSyntheticBlock(true); for (CompilerInput input : externs) { Node n = input.getAstRoot(this); if (hasErrors()) { return null; } externsRoot.addChildToBack(n); } // Check if the sources need to be re-ordered. if (options.manageClosureDependencies) { for (CompilerInput input : inputs) { input.setCompiler(this); // Forward-declare all the provided types, so that they // are not flagged even if they are dropped from the process. for (String provide : input.getProvides()) { getTypeRegistry().forwardDeclareType(provide); } } try { inputs = (moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph) .manageDependencies( options.manageClosureDependenciesEntryPoints, inputs); } catch (CircularDependencyException e) { report(JSError.make( JSModule.CIRCULAR_DEPENDENCY_ERROR, e.getMessage())); return null; } catch (MissingProvideException e) { report(JSError.make( MISSING_ENTRY_ERROR, e.getMessage())); return null; } } // Check if inputs need to be rebuilt from modules. boolean staleInputs = false; for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); if (hasErrors()) { return null; } // Inputs can have a null AST during initial parse. if (n == null) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> continue; } if (n.getJSDocInfo() != null) { JSDocInfo info = n.getJSDocInfo(); if (info.isExterns()) { // If the input file is explicitly marked as an externs file, then // assume the programmer made a mistake and throw it into // the externs pile anyways. externsRoot.addChildToBack(n); input.setIsExtern(true); input.getModule().remove(input); externs.add(input); staleInputs = true; } else if (info.isNoCompile()) { input.getModule().remove(input); staleInputs = true; } } } if (staleInputs) { fillEmptyModules(modules); rebuildInputsFromModules(); } // Build the AST. for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); if (n == null) { continue; } if (devMode) { runSanityCheck(); if (hasErrors()) { return null; } } if (options.sourceMapOutputPath != null || options.nameReferenceReportPath != null) { // Annotate the nodes in the tree with information from the // input file. This information is used to construct the SourceMap. SourceInformationAnnotator sia = new SourceInformationAnnotator( input.getName(), options.devMode != DevMode.OFF); NodeTraversal.traverse(this, n, sia); } jsRoot.addChildToBack(n); } externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot); externAndJsRoot.setIsSyntheticBlock(true); return externAndJsRoot; } finally { stopTracer(tracer, "parseInputs"); } } public Node parse(JSSourceFile file) { initCompilerOptionsIfTesting(); addToDebugLog("Parsing: " + file.getName()); return new JsAst(file).getAstRoot(this); } @Override Node parseSyntheticCode(String js) { CompilerInput input = new CompilerInput( JSSourceFile.fromCode(" [synthetic] ", js)); inputsByName.put(input.getName(), input); return input.getAstRoot(this); } void initCompilerOptionsIfTesting() { if (options == null) { // initialization for tests that don't initialize the compiler // by the normal mechanisms. initOptions(new CompilerOptions()); } } @Override Node parseSyntheticCode(String fileName, String js) { initCompilerOptionsIfTesting(); return parse(JSSourceFile.fromCode(fileName, js)); } @Override Node parseTestCode(String js) { initCompilerOptionsIfTesting(); CompilerInput input = new CompilerInput( JSSourceFile.fromCode(" [testcode] ", js)); if (inputsByName == null)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Inputs = inputs.size(); if (numInputs == 0) { return new String[0]; } String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException( "Bad module input: " + inputs.get(i).getName()); } cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; } }); } /** * Writes out js code from a root node. If printing input delimiters, this * method will attach a comment to the start of the text indicating which * input the output derived from. If there were any preserve annotations * within the root's source, they will also be printed in a block comment * at the beginning of the output. */ public void toSource(final CodeBuilder cb, final int inputSeqNum, final Node root) { runInCompilerThread(new Callable<Void>() { public Void call() throws Exception { if (options.printInputDelimiter) { if ((cb.getLength() > 0) && !cb.endsWith("\n")) { cb.append("\n"); // Make sure that the label starts on a new line } Preconditions.checkState(root.getType() == Token.SCRIPT); String delimiter = options.inputDelimiter; String sourceName = (String)root.getProp(Node.SOURCENAME_PROP); Preconditions.checkState(sourceName != null); Preconditions.checkState(!sourceName.isEmpty()); delimiter = delimiter.replaceAll("%name%", sourceName) .replaceAll("%num%", String.valueOf(inputSeqNum)); cb.append(delimiter) .append("\n"); } if (root.getJSDocInfo() != null && root.getJSDocInfo().getLicense() != null) { cb.append("/*\n") .append(root.getJSDocInfo().getLicense()) .append("*/\n"); } // If there is a valid source map, then indicate to it that the current // root node's mappings are offset by the given string builder buffer. if (options.sourceMapOutputPath != null) { sourceMap.setStartingPosition( cb.getLineIndex(), cb.getColumnIndex()); } String code = toSource(root, sourceMap); if (!code.isEmpty()) { cb.append(code); if (!code.endsWith(";")) { cb.append(";"); } } return null; } }); } /** * Generates JavaScript source code for an AST, doesn't generate source * map info. */ @Override String toSource(Node n) { initCompilerOptionsIfTesting(); return toSource(n, null);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> @Override public void reportCodeChange() { for (CodeChangeHandler handler : codeChangeHandlers) { handler.reportChange(); } } @Override public CodingConvention getCodingConvention() { CodingConvention convention = options.getCodingConvention(); convention = convention != null ? convention : defaultCodingConvention; return convention; } @Override public boolean isIdeMode() { return options.ideMode; } @Override public boolean acceptEcmaScript5() { return options.languageIn == LanguageMode.ECMASCRIPT5; } @Override Config getParserConfig() { if (parserConfig == null) { parserConfig = ParserRunner.createConfig( isIdeMode(), acceptEcmaScript5()); } return parserConfig; } @Override public boolean isTypeCheckingEnabled() { return options.checkTypes; } //------------------------------------------------------------------------ // Error reporting //------------------------------------------------------------------------ /** * The warning classes that are available from the command-line, and * are suppressable by the {@code @suppress} annotation. */ protected DiagnosticGroups getDiagnosticGroups() { return new DiagnosticGroups(); } @Override public void report(JSError error) { CheckLevel level = error.level; if (warningsGuard != null) { CheckLevel newLevel = warningsGuard.level(error); if (newLevel != null) { level = newLevel; } } if (level.isOn()) { errorManager.report(level, error); } } @Override public CheckLevel getErrorLevel(JSError error) { Preconditions.checkNotNull(options); WarningsGuard guards = options.getWarningsGuard(); if (guards == null) { return error.level; } else { return guards.level(error); } } /** * Report an internal error. */ @Override void throwInternalError(String message, Exception cause) { String finalMessage = "INTERNAL COMPILER ERROR.\n" + "Please report this problem.\n" + message; RuntimeException e = new RuntimeException(finalMessage, cause); if (cause != null) { e.setStackTrace(cause.getStackTrace()); } throw e; } /** * Gets the number of errors. */ public int getErrorCount() { return errorManager.getErrorCount(); } /** * Gets the number of warnings. */ public int getWarningCount() { return errorManager.getWarningCount(); } @Override boolean hasHaltingErrors() { return !isIdeMode() && getErrorCount() > 0; } /** * Consults the {@link ErrorManager} to see if we've encountered errors * that should halt compilation. <p> * * If {@link CompilerOptions#ideMode} is {@code true}, this function * always returns {@code false} without consulting the error manager. The * error manager will continue to

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2007 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Converts property accesses from quoted string syntax to dot syntax, where * possible. Dot syntax is more compact and avoids an object allocation in * IE 6. * */ class ConvertToDottedProperties extends AbstractPostOrderCallback implements CompilerPass { private final AbstractCompiler compiler; ConvertToDottedProperties(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.GETELEM: Node left = n.getFirstChild(); Node right = left.getNext(); if (right.getType() == Token.STRING && NodeUtil.isValidPropertyName(right.getString())) { n.removeChild(left); n.removeChild(right); parent.replaceChild(n, new Node(Token.GETPROP, left, right)); compiler.reportCodeChange(); } break; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.parsing.ParserRunner; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.io.IOException; import java.util.logging.Logger; /** * Generates an AST for a JavaScript source file. * */ public class JsAst implements SourceAst { private static final Logger logger_ = Logger.getLogger(JsAst.class.getName()); private static final long serialVersionUID = 1L; private transient SourceFile sourceFile; private String fileName; private Node root; public JsAst(SourceFile sourceFile) { this.sourceFile = sourceFile; this.fileName = sourceFile.getName(); } @Override public Node getAstRoot(AbstractCompiler compiler) { if (root == null) { createAst(compiler); } return root; } @Override public void clearAst() { root = null; // While we're at it, clear out any saved text in the source file on // the assumption that if we're dumping the parse tree, then we probably // assume regenerating everything else is a smart idea also. sourceFile.clearCachedSource(); } @Override public SourceFile getSourceFile() { return sourceFile; } @Override public void setSourceFile(SourceFile file) { Preconditions.checkState(fileName.equals(file.getName())); sourceFile = file; } private void createAst(AbstractCompiler compiler) { try { parse(compiler, sourceFile.getName(), sourceFile.getCode()); } catch (IOException e) { compiler.report( JSError.make(AbstractCompiler.READ_ERROR, sourceFile.getName())); } } private void parse(AbstractCompiler compiler, String sourceName, String sourceStr) { try { logger_.fine("Parsing: " + sourceName); root = ParserRunner.parse(sourceName, sourceStr, compiler.getParserConfig(), compiler.getDefaultErrorReporter(), logger_); } catch (IOException e) { compiler.report(JSError.make(AbstractCompiler.READ_ERROR, sourceName)); } if (root == null || compiler.hasHal

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>tingErrors()) { // There was a parse error or IOException, so use a dummy block. root = new Node(Token.BLOCK); } else { compiler.prepareAst(root); } // Set the source name so that the compiler passes can track // the source file and module. root.putProp(Node.SOURCENAME_PROP, sourceName); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Set; /** * Group a set of related diagnostic types together, so that they can * be toggled on and off as one unit. * @author nicksantos@google.com (Nick Santos) */ public class DiagnosticGroup { // The set of types represented by this group, hashed by key. private final Set<DiagnosticType> types; /** * Create a group that matches all errors of the given types. */ public DiagnosticGroup(DiagnosticType ...types) { this.types = ImmutableSet.copyOf(Arrays.asList(types)); } /** * Create a diagnostic group with no name that only matches the given type. */ private DiagnosticGroup(DiagnosticType type) { this.types = ImmutableSet.of(type); } // DiagnosticGroups with only a single DiagnosticType. private static final Map<DiagnosticType, DiagnosticGroup> singletons = Maps.newHashMap(); /** Create a diagnostic group that matches only the given type. */ static DiagnosticGroup forType(DiagnosticType type) { if (!singletons.containsKey(type)) { singletons.put(type, new DiagnosticGroup(type)); } return singletons.get(type); } /** * Create a composite group. */ public DiagnosticGroup(DiagnosticGroup ...groups) { Set<DiagnosticType> set = Sets.newHashSet(); for (DiagnosticGroup group : groups) { set.addAll(group.types); } this.types = ImmutableSet.copyOf(set); } /** * Returns whether the given error's type matches a type * in this group. */ public boolean matches(JSError error) { return matches(error.getType()); } /** * Returns whether the given type matches a type in this group. */ public boolean matches(DiagnosticType type) { return types.contains(type); } /** * Returns whether all of the types in the given group are in this group. */ boolean isSub

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * properties that are declared in the externs file. */ CrossModuleMethodMotion(AbstractCompiler compiler, IdGenerator idGenerator, boolean canModifyExterns) { this.compiler = compiler; this.idGenerator = idGenerator; this.moduleGraph = compiler.getModuleGraph(); this.analyzer = new AnalyzePrototypeProperties(compiler, moduleGraph, canModifyExterns, false); } public void process(Node externRoot, Node root) { // If there are < 2 modules, then we will never move anything, // so we're done. if (moduleGraph != null && moduleGraph.getModuleCount() > 1) { analyzer.process(externRoot, root); moveMethods(analyzer.getAllNameInfo()); } } /** * Move methods deeper in the module graph when possible. */ private void moveMethods(Collection<NameInfo> allNameInfo) { boolean hasStubDeclaration = idGenerator.hasGeneratedAnyIds(); for (NameInfo nameInfo : allNameInfo) { if (!nameInfo.isReferenced()) { // The code below can't do anything with unreferenced name // infos. They should be skipped to avoid NPE since their // deepestCommonModuleRef is null. continue; } if (nameInfo.readsClosureVariables()) { continue; } JSModule deepestCommonModuleRef = nameInfo.getDeepestCommonModuleRef(); if(deepestCommonModuleRef == null) { compiler.report(JSError.make(NULL_COMMON_MODULE_ERROR)); continue; } Iterator<Symbol> declarations = nameInfo.getDeclarations().descendingIterator(); while (declarations.hasNext()) { Symbol symbol = declarations.next(); if (!(symbol instanceof Property)) { continue; } Property prop = (Property) symbol; // We should only move a property across modules if: // 1) We can move it deeper in the module graph, and // 2) it's a function. // // #1 should be obvious. #2 is more subtle. It's possible // to copy off of a prototype, as in the code: // for (var k in Foo.prototype) { // doSomethingWith(Foo.prototype[k]); // } // This is a common way to implement pseudo-multiple inheritance in JS. // // So if we move a prototype method into a deeper module, we must // replace it with a stub function so that it preserves its original // behavior. Node value = prop.getValue(); if (moduleGraph.dependsOn(deepestCommonModuleRef, prop.getModule()) && value.getType() == Token.FUNCTION) { Node valueParent = prop.getValueParent(); Node proto = prop.getPrototype(); int stubId = idGenerator.newId(); // stub out the method in the original module valueParent.replaceChild(value, // A.prototype.b = JSCompiler_stubMethod

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(id); new Node(Token.CALL, Node.newString(Token.NAME, STUB_METHOD_NAME), Node.newNumber(stubId)) .copyInformationFromForTree(value)); // unstub the function body in the deeper module Node unstubParent = compiler.getNodeForCodeInsertion( deepestCommonModuleRef); unstubParent.addChildToFront( // A.prototype.b = JSCompiler_unstubMethod(id, body); new Node(Token.EXPR_RESULT, new Node(Token.ASSIGN, new Node(Token.GETPROP, proto.cloneTree(), Node.newString(Token.STRING, nameInfo.name)), new Node(Token.CALL, Node.newString(Token.NAME, UNSTUB_METHOD_NAME), Node.newNumber(stubId), value))) .copyInformationFromForTree(value)); compiler.reportCodeChange(); logger.fine("Moved method: " + proto.getQualifiedName() + "." + nameInfo.name + " from module " + prop.getModule() + " to module " + deepestCommonModuleRef); } } } if (!hasStubDeclaration && idGenerator.hasGeneratedAnyIds()) { // Declare stub functions in the top-most module. Node declarations = compiler.parseSyntheticCode(STUB_DECLARATIONS); compiler.getNodeForCodeInsertion(null).addChildrenToFront( declarations.removeChildren()); } } static class IdGenerator implements Serializable { private static final long serialVersionUID = 0L; /** * Ids for cross-module method stubbing, so that each method has * a unique id. */ private int currentId = 0; /** * Returns whether we've generated any new ids. */ boolean hasGeneratedAnyIds() { return currentId != 0; } /** * Creates a new id for stubbing a method. */ int newId() { return currentId++; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> resolve to any JavaScript type. It can only resolve to a named * {@link JSTypeRegistry} type, or to {@link FunctionType} or * {@link EnumType}.<p> * * If full typedefs are to be supported, then each method on each type class * needs to be reviewed to make sure that everything works correctly through * typedefs. Alternatively, we would need to walk through the parse tree and * unroll each reference to a {@code NamedType} to its resolved type before * applying the rest of the analysis.<p> * * TODO(user): Revisit all of this logic.<p> * * The existing typing logic is hacky. Unresolved types should get processed * in a more consistent way, but with the Rhino merge coming, there will be * much that has to be changed.<p> * */ class NamedType extends ProxyObjectType { private static final long serialVersionUID = 1L; private final String reference; private final String sourceName; private final int lineno; private final int charno; /** * Validates the type resolution. */ private Predicate<JSType> validator; /** * If true, don't warn about unresolveable type names. * * NOTE(nicksantos): A lot of third-party code doesn't use our type syntax. * They have code like * {@code @return} the bus. * and they clearly don't mean that "the" is a type. In these cases, we're * forgiving and try to guess whether or not "the" is a type when it's not * clear. */ private boolean forgiving = false; /** * Create a named type based on the reference. */ NamedType(JSTypeRegistry registry, String reference, String sourceName, int lineno, int charno) { super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); Preconditions.checkNotNull(reference); this.reference = reference; this.sourceName = sourceName; this.lineno = lineno; this.charno = charno; } @Override void forgiveUnknownNames() { forgiving = true; } /** Returns the type to which this refers (which is unknown if unresolved). */ public JSType getReferencedType() { return getReferencedTypeInternal(); } @Override public String getReferenceName() { return reference; } @Override public String toString() { return reference; } @Override public boolean hasReferenceName() { return true; } @Override boolean isNamedType() { return true; } @Override public boolean isNominalType() { return true; } /** * Two named types are equivalent if they are the same {@code * ObjectType} object. This is complicated by the fact that isEquivalent * is sometimes called before we have a chance to resolve the type * names. * *

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> @return {@code true} iff {@code that} == {@code this} or {@code that} * is a {@link NamedType} whose reference is the same as ours, * or {@code that} is the type we reference. */ @Override public boolean isEquivalentTo(JSType that) { if (this == that) { return true; } ObjectType objType = ObjectType.cast(that); if (objType != null) { return objType.isNominalType() && reference.equals(objType.getReferenceName()); } return false; } @Override public int hashCode() { return reference.hashCode(); } /** * Resolve the referenced type within the enclosing scope. */ @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) { // TODO(user): Investigate whether it is really necessary to keep two // different mechanisms for resolving named types, and if so, which order // makes more sense. Now, resolution via registry is first in order to // avoid triggering the warnings built into the resolution via properties. boolean resolved = resolveViaRegistry(t, enclosing); if (detectImplicitPrototypeCycle()) { handleTypeCycle(t); } if (resolved) { super.resolveInternal(t, enclosing); return registry.isLastGeneration() ? getReferencedType() : this; } resolveViaProperties(t, enclosing); if (detectImplicitPrototypeCycle()) { handleTypeCycle(t); } super.resolveInternal(t, enclosing); return registry.isLastGeneration() ? getReferencedType() : this; } /** * Resolves a named type by looking it up in the registry. * @return True if we resolved successfully. */ private boolean resolveViaRegistry( ErrorReporter t, StaticScope<JSType> enclosing) { JSType type = registry.getType(reference); if (type != null) { setReferencedAndResolvedType(type, t, enclosing); return true; } return false; } /** * Resolves a named type by looking up its first component in the scope, and * subsequent components as properties. The scope must have been fully * parsed and a symbol table constructed. */ private void resolveViaProperties(ErrorReporter t, StaticScope<JSType> enclosing) { JSType value = lookupViaProperties(t, enclosing); // last component of the chain if ((value instanceof FunctionType) && (value.isConstructor() || value.isInterface())) { FunctionType functionType = (FunctionType) value; setReferencedAndResolvedType( functionType.getInstanceType(), t, enclosing); } else if (value instanceof EnumType) { setReferencedAndResolvedType( ((EnumType) value).getElementsType(), t, enclosing); } else { // We've been running into issues where people forward-declare // non-named types. (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>This is legitimate...our dependency management // code doubles as our forward-declaration code.) // // So if the type does resolve to an actual value, but it's not named, // then don't respect the forward declaration. handleUnresolvedType(t, value == null || value.isUnknownType()); } } /** * Resolves a type by looking up its first component in the scope, and * subsequent components as properties. The scope must have been fully * parsed and a symbol table constructed. * @return The type of the symbol, or null if the type could not be found. */ private JSType lookupViaProperties( ErrorReporter t, StaticScope<JSType> enclosing) { String[] componentNames = reference.split("\\.", -1); if (componentNames[0].length() == 0) { return null; } StaticSlot<JSType> slot = enclosing.getSlot(componentNames[0]); if (slot == null) { return null; } // If the first component has a type of 'Unknown', then any type // names using it should be regarded as silently 'Unknown' rather than be // noisy about it. JSType slotType = slot.getType(); if (slotType == null || slotType.isAllType() || slotType.isNoType()) { return null; } JSType value = getTypedefType(t, slot, componentNames[0]); if (value == null) { return null; } // resolving component by component for (int i = 1; i < componentNames.length; i++) { ObjectType parentClass = ObjectType.cast(value); if (parentClass == null) { return null; } if (componentNames[i].length() == 0) { return null; } value = parentClass.getPropertyType(componentNames[i]); } return value; } private void setReferencedAndResolvedType(JSType type, ErrorReporter t, StaticScope<JSType> enclosing) { if (validator != null) { validator.apply(type); } setReferencedType(type); checkEnumElementCycle(t); setResolvedTypeInternal(getReferencedType()); } private void handleTypeCycle(ErrorReporter t) { setReferencedType( registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); t.warning("Cycle detected in inheritance chain of type " + reference, sourceName, lineno, null, charno); setResolvedTypeInternal(getReferencedType()); } private void checkEnumElementCycle(ErrorReporter t) { JSType referencedType = getReferencedType(); if (referencedType instanceof EnumElementType && ((EnumElementType) referencedType).getPrimitiveType() == this) { handleTypeCycle(t); } } // Warns about this type being unresolved iff it's not a forward-declared // type name. private void handleUnresolvedType(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> ErrorReporter t, boolean ignoreForwardReferencedTypes) { if (registry.isLastGeneration()) { boolean isForwardDeclared = ignoreForwardReferencedTypes && registry.isForwardDeclaredType(reference); boolean beForgiving = forgiving || isForwardDeclared; if (!beForgiving && registry.isLastGeneration()) { t.warning("Unknown type " + reference, sourceName, lineno, null, charno); } else { setReferencedType( registry.getNativeObjectType( JSTypeNative.CHECKED_UNKNOWN_TYPE)); if (registry.isLastGeneration() && validator != null) { validator.apply(getReferencedType()); } } setResolvedTypeInternal(getReferencedType()); } else { setResolvedTypeInternal(this); } } JSType getTypedefType(ErrorReporter t, StaticSlot<JSType> slot, String name) { JSType type = slot.getType(); if (type != null) { return type; } handleUnresolvedType(t, true); return null; } @Override public boolean setValidator(Predicate<JSType> validator) { // If the type is already resolved, we can validate it now. If // the type has not been resolved yet, we need to wait till its // resolved before we can validate it. if (this.isResolved()) { return super.setValidator(validator); } else { this.validator = validator; return true; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * <p>The syntactic scope creator scans the parse tree to create a Scope object * containing all the variable declarations in that scope.</p> * * <p>This implementation is not thread-safe.</p> * */ class SyntacticScopeCreator implements ScopeCreator { private final AbstractCompiler compiler; private Scope scope; private String sourceName; private final RedeclarationHandler redeclarationHandler; // The arguments variable is special, in that it's declared in every local // scope, but not explicitly declared. private static final String ARGUMENTS = "arguments"; public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR = DiagnosticType.error( "JSC_VAR_MULTIPLY_DECLARED_ERROR", "Variable {0} first declared in {1}"); public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR = DiagnosticType.error( "JSC_VAR_ARGUMENTS_SHADOWED_ERROR", "Shadowing \"arguments\" is not allowed"); /** * Creates a ScopeCreator. */ SyntacticScopeCreator(AbstractCompiler compiler) { this.compiler = compiler; this.redeclarationHandler = new DefaultRedeclarationHandler(); } SyntacticScopeCreator( AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) { this.compiler = compiler; this.redeclarationHandler = redeclarationHandler; } public Scope createScope(Node n, Scope parent) { sourceName = null; if (parent == null) { scope = new Scope(n, compiler); } else { scope = new Scope(parent, n); } scanRoot(n, parent); sourceName = null; Scope returnedScope = scope; scope = null; return returnedScope; } private void scanRoot(Node n, Scope parent) { if (n.getType() == Token.FUNCTION) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); final Node fnNameNode = n.getFirstChild

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(); final Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.getType() == Token.LP); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.getType() == Token.NAME); declareVar(a); } // Body scanVars(body, n); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n, null); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: sourceName = (String) n.getProp(Node.SOURCENAME_PROP); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child, n); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private class DefaultRedeclarationHandler implements RedeclarationHandler { public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.getType() == Token.CATCH && parent.getType() == Token.CATCH) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = false; JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); if (!allowDupe) { compiler.report( JSError.make(sourceName, n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(sourceName, n, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. * @param declaredType The variable's type, according to JSDoc */ private void declareVar(Node n) { Preconditions.checkState(n.getType() == Token.NAME); CompilerInput input = compiler.getInput(sourceName); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(node); if (inNodes.size() == 1) { FlowState<L> inNodeState = inNodes.get(0).getAnnotation(); state.setIn(inNodeState.getOut()); } else if (inNodes.size() > 1) { List<L> values = new ArrayList<L>(inNodes.size()); for (DiGraphNode<N, Branch> currentNode : inNodes) { FlowState<L> currentNodeState = currentNode.getAnnotation(); values.add(currentNodeState.getOut()); } state.setIn(joinOp.apply(values)); } } } else { List<DiGraphNode<N, Branch>> inNodes = cfg.getDirectedSuccNodes(node); if (inNodes.size() == 1) { DiGraphNode<N, Branch> inNode = inNodes.get(0); if (inNode == cfg.getImplicitReturn()) { state.setOut(createEntryLattice()); } else { FlowState<L> inNodeState = inNode.getAnnotation(); state.setOut(inNodeState.getIn()); } } else if (inNodes.size() > 1) { List<L> values = new ArrayList<L>(inNodes.size()); for (DiGraphNode<N, Branch> currentNode : inNodes) { FlowState<L> currentNodeState = currentNode.getAnnotation(); values.add(currentNodeState.getIn()); } state.setOut(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param <L> Input and output lattice element type. */ static class FlowState<L extends LatticeElement> implements Annotation { private L in; private L out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private FlowState(L inState, L outState) { Preconditions.checkNotNull(inState); Preconditions.checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } L getOut() { return out; } void setOut(L out) { Preconditions.checkNotNull(out); this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * The exception to be thrown if the analysis has been running for a long * number of iterations. Chances are the analysis is not monotonic, a * fixed-point cannot be found and it is currently stuck in an infinite loop

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>. */ static class MaxIterationsExceededException extends RuntimeException { private static final long serialVersionUID = 1L; MaxIterationsExceededException(String msg) { super(msg); } } abstract static class BranchedForwardDataFlowAnalysis <N, L extends LatticeElement> extends DataFlowAnalysis<N, L> { @Override protected void initialize() { orderedWorkSet.clear(); for (DiGraphNode<N, Branch> node : getCfg().getDirectedGraphNodes()) { int outEdgeCount = getCfg().getOutEdges(node.getValue()).size(); List<L> outLattices = Lists.newArrayList(); for (int i = 0; i < outEdgeCount; i++) { outLattices.add(createInitialEstimateLattice()); } node.setAnnotation(new BranchedFlowState<L>( createInitialEstimateLattice(), outLattices)); if (node != getCfg().getImplicitReturn()) { orderedWorkSet.add(node); } } } BranchedForwardDataFlowAnalysis(ControlFlowGraph<N> targetCfg, JoinOp<L> joinOp) { super(targetCfg, joinOp); } /** * Returns the lattice element at the exit point. Needs to be overridden * because we use a BranchedFlowState instead of a FlowState; ugh. */ @Override L getExitLatticeElement() { DiGraphNode<N, Branch> node = getCfg().getImplicitReturn(); BranchedFlowState<L> state = node.getAnnotation(); return state.getIn(); } @Override final boolean isForward() { return true; } /** * The branched flow function maps a single lattice to a list of output * lattices. * * <p>Each outgoing edge of a node will have a corresponding output lattice * in the ordered returned by * {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)} * in the returned list. * * @return A list of output values depending on the edge's branch type. */ abstract List<L> branchedFlowThrough(N node, L input); @Override protected final boolean flow(DiGraphNode<N, Branch> node) { BranchedFlowState<L> state = node.getAnnotation(); List<L> outBefore = state.out; state.out = branchedFlowThrough(node.getValue(), state.in); Preconditions.checkState(outBefore.size() == state.out.size()); for (int i = 0; i < outBefore.size(); i++) { if (!outBefore.get(i).equals(state.out.get(i))) { return true; } } return false; } @Override protected void joinInputs(DiGraphNode<N, Branch> node) { BranchedFlowState<L

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>> state = node.getAnnotation(); List<DiGraphNode<N, Branch>> predNodes = getCfg().getDirectedPredNodes(node); List<L> values = new ArrayList<L>(predNodes.size()); for (DiGraphNode<N, Branch> predNode : predNodes) { BranchedFlowState<L> predNodeState = predNode.getAnnotation(); L in = predNodeState.out.get( getCfg().getDirectedSuccNodes(predNode).indexOf(node)); values.add(in); } if (getCfg().getEntry() == node) { state.setIn(createEntryLattice()); } else if (!values.isEmpty()) { state.setIn(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param <L> Input and output lattice element type. */ static class BranchedFlowState<L extends LatticeElement> implements Annotation { private L in; private List<L> out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private BranchedFlowState(L inState, List<L> outState) { Preconditions.checkNotNull(inState); Preconditions.checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } List<L> getOut() { return out; } void setOut(List<L> out) { Preconditions.checkNotNull(out); for (L item : out) { Preconditions.checkNotNull(item); } this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * Compute set of escaped variables. When a variable is escaped in a * dataflow analysis, it can be reference outside of the code that we are * analyzing. A variable is escaped if any of the following is true: * * <p><ol> * <li>It is defined as the exception name in CATCH clause so it became a * variable local not to our definition of scope.</li> * <li>Exported variables as they can be needed after the script terminates. * </li> * <li>Names of named functions because in javascript, <i>function foo(){}</i> * does not kill <i>foo</i> in the dataflow.</li> */ static void computeEscaped(final Scope jsScope, final Set<Var> escaped, AbstractCompiler compiler) { // TODO(user):

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Very good place to store this information somewhere. AbstractPostOrderCallback finder = new AbstractPostOrderCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (jsScope == t.getScope() || !NodeUtil.isName(n) || NodeUtil.isFunction(parent)) { return; } String name = n.getString(); Var var = t.getScope().getVar(name); if (var != null && var.scope == jsScope) { escaped.add(jsScope.getVar(name)); } } }; NodeTraversal t = new NodeTraversal(compiler, finder); t.traverseAtScope(jsScope); // 1: Remove the exception name in CATCH which technically isn't local to // begin with. for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) { Var var = i.next(); if (var.getParentNode().getType() == Token.CATCH || compiler.getCodingConvention().isExported(var.getName())) { escaped.add(var); } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> transformed into {@code compiled}. */ private void test(String[] original, String[] compiled) { test(original, compiled, null); } /** * Asserts that when compiling with the given compiler options, * {@code original} is transformed into {@code compiled}. * If {@code warning} is non-null, we will also check if the given * warning type was emitted. */ private void test(String[] original, String[] compiled, DiagnosticType warning) { Compiler compiler = compile(original); if (warning == null) { assertEquals("Expected no warnings or errors\n" + "Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) + "Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()), 0, compiler.getErrors().length + compiler.getWarnings().length); } else { assertEquals(1, compiler.getWarnings().length); assertEquals(warning, compiler.getWarnings()[0].getType()); } Node root = compiler.getRoot().getLastChild(); if (useStringComparison) { assertEquals(Joiner.on("").join(compiled), compiler.toSource()); } else { Node expectedRoot = parse(compiled); String explanation = expectedRoot.checkTreeEquals(root); assertNull("\nExpected: " + compiler.toSource(expectedRoot) + "\nResult: " + compiler.toSource(root) + "\n" + explanation, explanation); } } /** * Asserts that when compiling, there is an error or warning. */ private void test(String original, DiagnosticType warning) { test(new String[] { original }, warning); } /** * Asserts that when compiling, there is an error or warning. */ private void test(String[] original, DiagnosticType warning) { Compiler compiler = compile(original); assertEquals("Expected exactly one warning or error " + "Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) + "Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()), 1, compiler.getErrors().length + compiler.getWarnings().length); assertTrue(exitCodes.size() > 0); int lastExitCode = exitCodes.get(exitCodes.size() - 1); if (compiler.getErrors().length > 0) { assertEquals(1, compiler.getErrors().length); assertEquals(warning, compiler.getErrors()[0].getType()); assertEquals(1, lastExitCode); } else { assertEquals(1, compiler.getWarnings().length); assertEquals(warning, compiler.getWarnings()[0].getType()); assertEquals(0, lastExitCode); } } private CommandLineRunner createCommandLineRunner(String[] original) { for (int i = 0; i < original.length; i++) { args.add("--js"); args.add("/path/to/input" + i

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> @throws IOException */ public static List<JSSourceFile> getDefaultExterns() throws IOException { InputStream input = CommandLineRunner.class.getResourceAsStream( "/externs.zip"); ZipInputStream zip = new ZipInputStream(input); Map<String, JSSourceFile> externsMap = Maps.newHashMap(); for (ZipEntry entry = null; (entry = zip.getNextEntry()) != null; ) { LimitInputStream entryStream = new LimitInputStream(zip, entry.getSize()); externsMap.put(entry.getName(), JSSourceFile.fromInputStream( // Give the files an odd prefix, so that they do not conflict // with the user's files. "externs.zip//" + entry.getName(), entryStream)); } Preconditions.checkState( externsMap.keySet().equals(Sets.newHashSet(DEFAULT_EXTERNS_NAMES)), "Externs zip must match our hard-coded list of externs."); // Order matters, so the resources must be added to the result list // in the expected order. List<JSSourceFile> externs = Lists.newArrayList(); for (String key : DEFAULT_EXTERNS_NAMES) { externs.add(externsMap.get(key)); } return externs; } /** * @return Whether the configuration is valid. */ public boolean shouldRunCompiler() { return this.isConfigValid; } /** * Runs the Compiler. Exits cleanly in the event of an error. */ public static void main(String[] args) { CommandLineRunner runner = new CommandLineRunner(args); if (runner.shouldRunCompiler()) { runner.run(); } else { System.exit(-1); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>> dest = getNodeOrFail(destValue); LinkedUndirectedGraphEdge<N, E> edge = useEdgeAnnotations ? new AnnotatedLinkedUndirectedGraphEdge<N, E>(src, edgeValue, dest) : new LinkedUndirectedGraphEdge<N, E>(src, edgeValue, dest); src.getNeighborEdges().add(edge); dest.getNeighborEdges().add(edge); } @Override public void disconnect(N srcValue, N destValue) { LinkedUndirectedGraphNode<N, E> src = getNodeOrFail(srcValue); LinkedUndirectedGraphNode<N, E> dest = getNodeOrFail(destValue); for (UndiGraphEdge<N, E> edge : getUndirectedGraphEdges(srcValue, destValue)) { src.getNeighborEdges().remove(edge); dest.getNeighborEdges().remove(edge); } } @Override public UndiGraphNode<N, E> createUndirectedGraphNode( N nodeValue) { LinkedUndirectedGraphNode<N, E> node = nodes.get(nodeValue); if (node == null) { node = useNodeAnnotations ? new AnnotatedLinkedUndirectedGraphNode<N, E>(nodeValue) : new LinkedUndirectedGraphNode<N, E>(nodeValue); nodes.put(nodeValue, node); } return node; } @Override public List<GraphNode<N, E>> getNeighborNodes(N value) { UndiGraphNode<N, E> uNode = getUndirectedGraphNode(value); List<GraphNode<N, E>> nodeList = Lists.newArrayList(); for (Iterator<GraphNode<N, E>> i = getNeighborNodesIterator(value); i.hasNext();) { nodeList.add(i.next()); } return nodeList; } @Override public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) { UndiGraphNode<N, E> uNode = getUndirectedGraphNode(value); Preconditions.checkNotNull(uNode, value + " should be in the graph."); return ((LinkedUndirectedGraphNode<N, E>) uNode).neighborIterator(); } @SuppressWarnings("unchecked") @Override public List<UndiGraphEdge<N, E>> getUndirectedGraphEdges(N n1, N n2) { UndiGraphNode<N, E> dNode1 = nodes.get(n1); if (dNode1 == null) { return null; } UndiGraphNode<N, E> dNode2 = nodes.get(n2); if (dNode2 == null) { return null; } List<UndiGraphEdge<N, E>> edges = Lists.newArrayList(); for (UndiGraphEdge<N, E> outEdge : dNode1.getNeighborEdges()) { if (outEdge.getNodeA() == dNode2 ||

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import java.nio.charset.Charset; import java.util.Set; /** * A code generator that outputs type annotations for functions and * constructors. */ class TypedCodeGenerator extends CodeGenerator { TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) { super(consumer, outputCharset); } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.getType() == Token.BLOCK || parent.getType() == Token.SCRIPT)) { if (n.getType() == Token.FUNCTION) { add(getFunctionAnnotation(n)); } else if (n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.getType() == Token.VAR && n.getFirstChild().getFirstChild() != null && n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) { add(getFunctionAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); } private String getTypeAnnotation(Node node) { JSType type = node.getJSType(); if (type instanceof FunctionType) { return getFunctionAnnotation(node); } else if (type != null && !type.isUnknownType() && !type.isEmptyType() && !type.isVoidType() && !type.isFunctionPrototypeType()) { return "/** @type {" + node.getJSType() + "} */\n"; } else { return ""; } } /** * @param fnNode A node for a function for which to generate a type annotation */ private String getFunctionAnnotation(Node fnNode)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { Preconditions.checkState(fnNode.getType() == Token.FUNCTION); StringBuilder sb = new StringBuilder("/**\n"); JSType type = fnNode.getJSType(); if (type == null || type.isUnknownType()) { return ""; } FunctionType funType = (FunctionType) fnNode.getJSType(); // We need to use the child nodes of the function as the nodes for the // parameters of the function type do not have the real parameter names. // FUNCTION // NAME // LP // NAME param1 // NAME param2 if (fnNode != null) { Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild(); // Param types for (Node n : funType.getParameters()) { // Bail out if the paramNode is not there. if (paramNode == null) { break; } sb.append(" * @param {" + getParameterNodeJSDocType(n) + "} "); sb.append(paramNode.getString()); sb.append("\n"); paramNode = paramNode.getNext(); } } // Return type JSType retType = funType.getReturnType(); if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) { sb.append(" * @return {" + retType + "}\n"); } // Constructor/interface if (funType.isConstructor() || funType.isInterface()) { FunctionType superConstructor = funType.getSuperClassConstructor(); if (superConstructor != null) { ObjectType superInstance = funType.getSuperClassConstructor().getInstanceType(); if (!superInstance.toString().equals("Object")) { sb.append(" * @extends {" + superInstance + "}\n"); } } // Avoid duplicates, add implemented type to a set first Set<String> interfaces = Sets.newTreeSet(); for (ObjectType interfaze : funType.getImplementedInterfaces()) { interfaces.add(interfaze.toString()); } for (String interfaze : interfaces) { sb.append(" * @implements {" + interfaze + "}\n"); } if (funType.isConstructor()) { sb.append(" * @constructor\n"); } else if (funType.isInterface()) { sb.append(" * @interface\n"); } } if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) { sb.append(" * @javadispatch\n"); } sb.append(" */\n"); return sb.toString(); } /** * Creates a JSDoc-suitable String representation the type of a parameter. * * @param parameterNode The parameter node. */ private String getParameterNodeJSDocType(Node parameterNode) { JSType parameterType = parameterNode.getJSType(); String typeString; // Emit unknown types

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Find all Functions, VARs, and Exception names and make them * unique. Specifically, it will not modify object properties. * @author johnlenz@google.com (John Lenz) * TODO(johnlenz): Try to merge this with the ScopeCreator. */ class MakeDeclaredNamesUnique implements NodeTraversal.ScopedCallback { public static final String ARGUMENTS = "arguments"; private Deque<Renamer> nameStack = new ArrayDeque<Renamer>(); private final Renamer rootRenamer; MakeDeclaredNamesUnique() { this(new ContextualRenamer()); } MakeDeclaredNamesUnique(Renamer renamer) { this.rootRenamer = renamer; } static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) { return new ContextualRenameInverter(compiler); } @Override public void enterScope(NodeTraversal t) { Node declarationRoot = t.getScopeRoot(); Renamer renamer; if (nameStack.isEmpty()) { // If the contextual renamer is being used the starting context can not // be a function. Preconditions.checkState( declarationRoot.getType() != Token.FUNCTION || !(rootRenamer instanceof ContextualRenamer));

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Preconditions.checkState(t.inGlobalScope()); renamer = rootRenamer; } else { renamer = nameStack.peek().forChildScope(); } if (declarationRoot.getType() == Token.FUNCTION) { // Add the function parameters Node fnParams = declarationRoot.getFirstChild().getNext(); for (Node c = fnParams.getFirstChild(); c != null; c = c.getNext()) { String name = c.getString(); renamer.addDeclaredName(name); } // Add the function body declarations Node functionBody = declarationRoot.getLastChild(); findDeclaredNames(functionBody, null, renamer); } else { // Add the block declarations findDeclaredNames(declarationRoot, null, renamer); } nameStack.push(renamer); } @Override public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { nameStack.pop(); } } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.FUNCTION: { // Add recursive function name, if needed. // NOTE: "enterScope" is called after we need to pick up this name. Renamer renamer = nameStack.peek().forChildScope(); // If needed, add the function recursive name. String name = n.getFirstChild().getString(); if (name != null && !name.isEmpty() && parent != null && !NodeUtil.isFunctionDeclaration(n)) { renamer.addDeclaredName(name); } nameStack.push(renamer); } break; case Token.CATCH: { Renamer renamer = nameStack.peek().forChildScope(); String name = n.getFirstChild().getString(); renamer.addDeclaredName(name); nameStack.push(renamer); } break; } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: String newName = getReplacementName(n.getString()); if (newName != null) { Renamer renamer = nameStack.peek(); if (renamer.stripConstIfReplaced()) { // TODO(johnlenz): Do we need to do anything about the javadoc? n.removeProp(Node.IS_CONSTANT_NAME); } n.setString(newName); t.getCompiler().reportCodeChange(); } break; case Token.FUNCTION: // Remove function recursive name (if any). nameStack.pop(); break; case Token.CATCH: // Remove catch except name from the stack of names. nameStack.pop(); break; } } /** * Walks the stack of name maps and finds the replacement name for the * current scope. */ private String get

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ReplacementName(String oldName) { for (Renamer names : nameStack) { String newName = names.getReplacementName(oldName); if (newName != null) { return newName; } } return null; } /** * Traverses the current scope and collects declared names. Does not * decent into functions or add CATCH exceptions. */ private void findDeclaredNames(Node n, Node parent, Renamer renamer) { // Do a shallow traversal, so don't traverse into function declarations, // except for the name of the function itself. if (parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild()) { if (NodeUtil.isVarDeclaration(n)) { renamer.addDeclaredName(n.getString()); } else if (NodeUtil.isFunctionDeclaration(n)) { Node nameNode = n.getFirstChild(); renamer.addDeclaredName(nameNode.getString()); } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { findDeclaredNames(c, n, renamer); } } } /** * Declared names renaming policy interface. */ interface Renamer { /** * Called when a declared name is found in the local current scope. */ void addDeclaredName(String name); /** * @return A replacement name, null if oldName is unknown or should not * be replaced. */ String getReplacementName(String oldName); /** * @return Whether the constant-ness of a name should be removed. */ boolean stripConstIfReplaced(); /** * @return A Renamer for a scope within the scope of the current Renamer. */ Renamer forChildScope(); } /** * Inverts the transformation by {@link ContextualRenamer}, when possible. */ static class ContextualRenameInverter implements ScopedCallback, CompilerPass { private final AbstractCompiler compiler; // The set of names referenced in the current scope. private Set<String> referencedNames = ImmutableSet.of(); // Stack reference sets. private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>(); // Name are globally unique initially, so we don't need a per-scope map. private Map<String, List<Node>> nameMap = Maps.newHashMap(); private ContextualRenameInverter(AbstractCompiler compiler) { this.compiler = compiler; } public void process(Node externs, Node js) { NodeTraversal.traverse(compiler, js, this); } public static String getOrginalName(String name) { int index = indexOfSeparator(name); return (index == -1) ? name : name.substring(0, index); } private static int indexOfSeparator(String name) { return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR); } private boolean containsSeparator(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>String name) { return name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1; } /** * Prepare a set for the new scope. */ public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } referenceStack.push(referencedNames); referencedNames = Sets.newHashSet(); } /** * Rename vars for the current scope, and merge any referenced * names into the parent scope reference set. */ public void exitScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); handleScopeVar(v); } // Merge any names that were referenced but not declared in the current // scope. Set<String> current = referencedNames; referencedNames = referenceStack.pop(); // If there isn't anything left in the stack we will be going into the // global scope: don't try to build a set of referenced names for the // global scope. if (!referenceStack.isEmpty()) { referencedNames.addAll(current); } } /** * For the Var declared in the current scope determine if it is possible * to revert the name to its orginal form without conflicting with other * values. */ void handleScopeVar(Var v) { String name = v.getName(); if (containsSeparator(name) && !getOrginalName(name).isEmpty()) { String newName = findReplacementName(name); referencedNames.remove(name); // Adding a reference to the new name to prevent either the parent // scopes or the current scope renaming another var to this new name. referencedNames.add(newName); List<Node> references = nameMap.get(name); Preconditions.checkState(references != null); for (Node n : references) { Preconditions.checkState(n.getType() == Token.NAME); n.setString(newName); } compiler.reportCodeChange(); nameMap.remove(name); } } /** * Find a name usable in the local scope. */ private String findReplacementName(String name) { String original = getOrginalName(name); String newName = original; int i = 0; while (!isValidName(newName)) { newName = original + ContextualRenamer.UNIQUE_ID_SEPARATOR + String.valueOf(i++); } return newName; } /** * @return Whether the name is valid to use in the local scope. */ private boolean isValidName(String name) { if (TokenStream.isJSIdentifier(name) && !referencedNames.contains(name) && !name.equals(ARGUMENTS)) { return true; } return false; } @Override public boolean shouldTraverse(NodeTraversal t, Node n

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; } private void reserveName(String name) { nameUsage.setCount(name, 0, 1); } private int incrementNameCount(String name) { return nameUsage.add(name, 1); } @Override public boolean stripConstIfReplaced() { return false; } } /** * Rename every declared name to be unique. Typically this would be used * when injecting code to insure that names do not conflict with existing * names. * * Used by the FunctionInjector * @see FunctionInjector */ static class InlineRenamer implements Renamer { private final Map<String, String> declarations = Maps.newHashMap(); private final Supplier<String> uniqueIdSupplier; private final String idPrefix; private final boolean removeConstness; InlineRenamer( Supplier<String> uniqueIdSupplier, String idPrefix, boolean removeConstness) { this.uniqueIdSupplier = uniqueIdSupplier; // To ensure that the id does not conflict with the id from the // ContextualRenamer some prefix is needed. Preconditions.checkArgument(!idPrefix.isEmpty()); this.idPrefix = idPrefix; this.removeConstness = removeConstness; } @Override public void addDeclaredName(String name) { Preconditions.checkState(!name.equals(ARGUMENTS)); if (!declarations.containsKey(name)) { declarations.put(name, getUniqueName(name)); } } private String getUniqueName(String name) { if (name.isEmpty()) { return name; } if (name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1) { name = name.substring( 0, name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR)); } // By using the same separator the id will be stripped if it isn't // needed when variable renaming is turned off. return name + ContextualRenamer.UNIQUE_ID_SEPARATOR + idPrefix + uniqueIdSupplier.get(); } @Override public String getReplacementName(String oldName) { return declarations.get(oldName); } @Override public Renamer forChildScope() { return new InlineRenamer(uniqueIdSupplier, idPrefix, removeConstness); } @Override public boolean stripConstIfReplaced() { return removeConstness; } } /** * For injecting boilerplate libraries. Leaves global names alone * and renames local names like InlineRenamer. */ static class BoilerplateRenamer extends ContextualRenamer { private final Supplier<String> uniqueIdSupplier; private final String idPrefix; BoilerplateRenamer( Supplier<String> uniqueIdSupplier, String idPrefix) { this.uniqueIdSupplier = uniqueIdSupplier; this.idPrefix = idPrefix; } @Override public Renamer forChildScope() { return new InlineRenamer(uniqueIdSupplier, idPrefix

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Check for invalid breaks and continues in the program. * */ class ControlStructureCheck implements CompilerPass { private AbstractCompiler compiler; private String sourceName = null; static final DiagnosticType USE_OF_WITH = DiagnosticType.warning( "JSC_USE_OF_WITH", "The use of the 'with' structure should be avoided."); ControlStructureCheck(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { check(root); } /** * Reports errors for any invalid use of control structures. * * @param node Current node to check. */ private void check(Node node) { switch (node.getType()) { case Token.WITH: JSDocInfo info = node.getJSDocInfo(); boolean allowWith = info != null && info.getSuppressions().contains("with"); if (!allowWith) { report(node, USE_OF_WITH); } break; case Token.SCRIPT: // Remember the source file name in case we need to report an error. sourceName = (String) node.getProp(Node.SOURCENAME_PROP); break; } for (Node bChild = node.getFirstChild(); bChild != null;) { Node next = bChild.getNext(); check(bChild); bChild = next; } } private void report(Node n, DiagnosticType error) { compiler.report(JSError.make(sourceName, n, error)); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ForIn(nextSibling)) { Node forNode = nextSibling; Node forVar = forNode.getFirstChild(); if (NodeUtil.isName(forVar) && NodeUtil.isVar(n) && n.hasOneChild()) { Node name = n.getFirstChild(); if (!name.hasChildren() && forVar.getString().equals(name.getString())) { // Ok, the names match, and the var declaration does not have an // initializer. Move it into the loop. parent.removeChild(n); forNode.replaceChild(forVar, n); compiler.reportCodeChange(); } } } else if (nextSibling.getType() == Token.FOR && nextSibling.getFirstChild().getType() == Token.EMPTY) { // Does the current node contain an in operator? If so, embedding // the expression in a for loop can cause some Javascript parsers (such // as the Playstation 3's browser based on Access's NetFront // browser) to fail to parse the code. // See bug 1778863 for details. if (NodeUtil.containsType(n, Token.IN)) { return; } // Move the current node into the FOR loop initializer. Node forNode = nextSibling; Node oldInitializer = forNode.getFirstChild(); parent.removeChild(n); Node newInitializer; if (NodeUtil.isVar(n)) { newInitializer = n; } else { // Extract the expression from EXPR_RESULT node. Preconditions.checkState(n.hasOneChild()); newInitializer = n.getFirstChild(); n.removeChild(newInitializer); } forNode.replaceChild(oldInitializer, newInitializer); compiler.reportCodeChange(); } } static class StripConstantAnnotations extends AbstractPostOrderCallback implements CompilerPass { private AbstractCompiler compiler; StripConstantAnnotations(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node js) { NodeTraversal.traverse(compiler, externs, this); NodeTraversal.traverse(compiler, js, this); } @Override public void visit(NodeTraversal t, Node node, Node parent) { if (node.getType() == Token.NAME || node.getType() == Token.STRING) { node.removeProp(Node.IS_CONSTANT_NAME); } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> been deprecated: {2}"); static final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled( "JSC_DEPRECATED_CLASS", "Class {0} has been deprecated."); static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled( "JSC_DEPRECATED_CLASS_REASON", "Class {0} has been deprecated: {1}"); static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_GLOBAL_ACCESS", "Access to private variable {0} not allowed outside file {1}."); static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_PROPERTY_ACCESS", "Access to private property {0} of {1} not allowed here."); static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PROTECTED_PROPERTY_ACCESS", "Access to protected property {0} of {1} not allowed here."); static final DiagnosticType PRIVATE_OVERRIDE = DiagnosticType.disabled( "JSC_PRIVATE_OVERRIDE", "Overriding private property of {0}."); static final DiagnosticType VISIBILITY_MISMATCH = DiagnosticType.disabled( "JSC_VISIBILITY_MISMATCH", "Overriding {0} property of {1} with {2} property."); private final AbstractCompiler compiler; private final TypeValidator validator; // State about the current traversal. private int deprecatedDepth = 0; private int methodDepth = 0; private JSType currentClass = null; CheckAccessControls(AbstractCompiler compiler) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); } public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n, parent)) { deprecatedDepth++; } if (methodDepth == 0) { currentClass = getClassOfMethod(n, parent); } methodDepth++; } } public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n, parent)) { deprecatedDepth--; } methodDepth--; if (methodDepth == 0) { currentClass = null; } } } /** * Gets the type of the class that "owns" a method, or null if * we know that its un-owned. */ private JSType getClassOfMethod(Node n, Node parent) { if (parent.getType() == Token.ASSIGN) { Node lValue = parent.getFirstChild(); if (lValue.isQualifiedName()) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (lValue.getType() == Token.GETPROP) { // We have an assignment of the form "a.b = ...". JSType lValueType = lValue.getJSType(); if (lValueType != null && lValueType.isConstructor()) { // If a.b is a constructor, then everything in this function // belongs to the "a.b" type. return ((FunctionType) lValueType).getInstanceType(); } else { // If a.b is not a constructor, then treat this as a method // of whatever type is on "a". return normalizeClassType(lValue.getFirstChild().getJSType()); } } else { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } } else if (NodeUtil.isFunctionDeclaration(n) || parent.getType() == Token.NAME) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isConstructor()) { return ((FunctionType) type).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } } /** * Checks the given NAME node to ensure that access restrictions are obeyed. */ private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.NEW) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( t.makeError(n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( t.makeError(n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking constructors. if (parent.getType() == Token.NEW) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE && !t.getInput().getName().equals(docInfo.getSourceName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } compiler.report

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), docInfo.getSourceName())); } } } } /** * Determines whether the given property is visible in the current context. * @param t The current traversal. * @param getprop The getprop node. */ private void checkPropertyVisibility(NodeTraversal t, Node getprop, Node parent) { ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); if (objectType != null) { // Is this a normal property access, or are we trying to override // an existing property? boolean isOverride = t.inGlobalScope() && parent.getType() == Token.ASSIGN && parent.getFirstChild() == getprop; // Find the lowest property defined on a class with visibility // information. if (isOverride) { objectType = objectType.getImplicitPrototype(); } JSDocInfo docInfo = null; for (; objectType != null; objectType = objectType.getImplicitPrototype()) { docInfo = objectType.getOwnPropertyJSDocInfo(propertyName); if (docInfo != null && docInfo.getVisibility() != Visibility.INHERITED) { break; } } if (objectType == null) { // We couldn't find a visibility modifier; assume it's public. return; } boolean sameInput = t.getInput().getName().equals(docInfo.getSourceName()); Visibility visibility = docInfo.getVisibility(); JSType ownerType = normalizeClassType(objectType); if (isOverride) { // Check an ASSIGN statement that's trying to override a property // on a superclass. JSDocInfo overridingInfo = parent.getJSDocInfo(); Visibility overridingVisibility = overridingInfo == null ? Visibility.INHERITED : overridingInfo.getVisibility(); // Check that (a) the property *can* be overridden, and // (b) that the visibility of the override is the same as the // visibility of the original property. if (visibility == Visibility.PRIVATE && !sameInput) { compiler.report( t.makeError(getprop, PRIVATE_OVERRIDE, objectType.toString())); } else if (overridingVisibility != Visibility.INHERITED && overridingVisibility != visibility) { compiler.report( t.makeError(getprop, VISIBILITY_MISMATCH, visibility.name(), objectType.toString(), overridingVisibility.name())); } } else { if (sameInput) { // private access is always allowed in the same file. return; } else if (visibility == Visibility.PRIVATE && (currentClass == null || ownerType.differsFrom(currentClass))) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } // private access is not allowed outside the file

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> from a different // enclosing class. compiler.report( t.makeError(getprop, BAD_PRIVATE_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } else if (visibility == Visibility.PROTECTED) { // There are 3 types of legal accesses of a protected property: // 1) Accesses in the same file // 2) Overriding the property in a subclass // 3) Accessing the property from inside a subclass // The first two have already been checked for. if (currentClass == null || !currentClass.isSubtype(ownerType)) { compiler.report( t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } } } } } /** * Whether the given access of a private constructor is legal. * * For example, * new PrivateCtor_(); // not legal * PrivateCtor_.newInstance(); // legal * x instanceof PrivateCtor_ // legal * * This is a weird special case, because our visibility system is inherited * from Java, and JavaScript has no distinction between classes and * constructors like Java does. * * We may want to revisit this if we decide to make the restrictions tighter. */ private static boolean isValidPrivateConstructorAccess(Node parent) { return parent.getType() != Token.NEW; } /** * Determines whether a deprecation warning should be emitted. * @param t The current traversal. * @param n The node which we are checking. * @param parent The parent of the node which we are checking. */ private boolean shouldEmitDeprecationWarning( NodeTraversal t, Node n, Node parent) { // In the global scope, there are only two kinds of accesses that should // be flagged for warnings: // 1) Calls of deprecated functions and methods. // 2) Instantiations of deprecated classes. // For now, we just let everything else by. if (t.inGlobalScope()) { if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) || n.getType() == Token.NEW)) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.getType() == Token.GETPROP && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently ok to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated. */ private static boolean isDeprecatedFunction(Node n, Node parent) { if (n.getType() == Token.FUNCTION) { JSType type = n.getJSType(); if (type != null) { return getTypeDeprecationInfo(type) != null; } } return false; } /** * Returns the deprecation reason for the type if it is marked * as being deprecated. Returns empty string if the type is deprecated * but no reason was given. Returns null if the type is not deprecated. */ private static String getTypeDeprecationInfo(JSType type) { if (type == null) { return null; } JSDocInfo info = type.getJSDocInfo(); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType objType = ObjectType.cast(type); if (objType != null) { ObjectType implicitProto = objType.getImplicitPrototype(); if (implicitProto != null) { return getTypeDeprecationInfo(implicitProto); } } return null; } /** * Returns the deprecation reason for the property if it is marked * as being deprecated. Returns empty string if the property is deprecated * but no reason was given. Returns null if the property is not deprecated. */ private static String getPropertyDeprecationInfo(ObjectType type, String prop) { JSDocInfo info = type.getOwnPropertyJSDocInfo(prop); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType implicitProto = type.getImplicitPrototype(); if (implicitProto != null) { return getPropertyDeprecationInfo(implicitProto, prop); } return null; } /** * Dereference a type, autoboxing it and filtering out null. */ private static JSType dereference(JSType type) { return type == null ? null : type.dereference(); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>PassFactory pass : passes) { Preconditions.checkState(pass.isOneTimePass()); } } /** Verify that all the passes are multi-run passes. */ private void assertAllLoopablePasses(List<PassFactory> passes) { for (PassFactory pass : passes) { Preconditions.checkState(!pass.isOneTimePass()); } } /** Checks for validity of the control structures. */ private final PassFactory checkControlStructures = new PassFactory("checkControlStructures", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new ControlStructureCheck(compiler); } }; /** Checks that all constructed classes are goog.require()d. */ private final PassFactory checkRequires = new PassFactory("checkRequires", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckRequiresForConstructors(compiler, options.checkRequires); } }; /** Makes sure @constructor is paired with goog.provides(). */ private final PassFactory checkProvides = new PassFactory("checkProvides", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckProvides(compiler, options.checkProvides); } }; private static final DiagnosticType GENERATE_EXPORTS_ERROR = DiagnosticType.error( "JSC_GENERATE_EXPORTS_ERROR", "Exports can only be generated if export symbol/property " + "functions are set."); /** Generates exports for @export annotations. */ private final PassFactory generateExports = new PassFactory("generateExports", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { CodingConvention convention = compiler.getCodingConvention(); if (convention.getExportSymbolFunction() != null && convention.getExportPropertyFunction() != null) { return new GenerateExports(compiler, convention.getExportSymbolFunction(), convention.getExportPropertyFunction()); } else { return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR); } } }; /** Generates exports for functions associated with JSUnit. */ private final PassFactory exportTestFunctions = new PassFactory("exportTestFunctions", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { CodingConvention convention = compiler.getCodingConvention(); if (convention.getExportSymbolFunction() != null) { return new ExportTestFunctions(compiler, convention.getExportSymbolFunction()); } else { return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR); } } }; /** Raw exports processing pass. */ final PassFactory gatherRawExports = new PassFactory("gatherRawExports", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { final GatherRawExports pass = new GatherRawExports( compiler); return new CompilerPass() { @Override public void process(Node externs, Node root) { pass.process(externs,

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> root); if (exportedNames == null) { exportedNames = Sets.newHashSet(); } exportedNames.addAll(pass.getExportedVariableNames()); } }; } }; /** Closure pre-processing pass. */ @SuppressWarnings("deprecation") final PassFactory closurePrimitives = new PassFactory("processProvidesAndRequires", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { final ProcessClosurePrimitives pass = new ProcessClosurePrimitives( compiler, options.brokenClosureRequiresLevel, options.rewriteNewDateGoogNow); return new CompilerPass() { @Override public void process(Node externs, Node root) { pass.process(externs, root); exportedNames = pass.getExportedVariableNames(); } }; } }; /** * The default i18n pass. * A lot of the options are not configurable, because ReplaceMessages * has a lot of legacy logic. */ private final PassFactory replaceMessages = new PassFactory("replaceMessages", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new ReplaceMessages(compiler, options.messageBundle, /* warn about message dupes */ true, /* allow messages with goog.getMsg */ JsMessage.Style.getFromParams(true, false), /* if we can't find a translation, don't worry about it. */ false); } }; /** Applies aliases and inlines goog.scope. */ final PassFactory closureGoogScopeAliases = new PassFactory("processGoogScopeAliases", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new ScopedAliases(compiler); } }; /** Checks that CSS class names are wrapped in goog.getCssName */ private final PassFactory closureCheckGetCssName = new PassFactory("checkMissingGetCssName", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { String blacklist = options.checkMissingGetCssNameBlacklist; Preconditions.checkState(blacklist != null && !blacklist.isEmpty(), "Not checking use of goog.getCssName because of empty blacklist."); return new CheckMissingGetCssName( compiler, options.checkMissingGetCssNameLevel, blacklist); } }; /** * Processes goog.getCssName. The cssRenamingMap is used to lookup * replacement values for the classnames. If null, the raw class names are * inlined. */ private final PassFactory closureReplaceGetCssName = new PassFactory("renameCssNames", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { Map<String, Integer> newCssNames = null; if (options.gatherCssNames) { newCssNames = Maps.newHashMap(); } (new ReplaceCssNames(compiler, new

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> final PassFactory inferTypes = new PassFactory("inferTypes", false) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(typedScopeCreator); makeTypeInference(compiler).process(externs, root); } }; } }; /** Checks type usage */ private final PassFactory checkTypes = new PassFactory("checkTypes", false) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(typedScopeCreator); TypeCheck check = makeTypeCheck(compiler); check.process(externs, root); compiler.getErrorManager().setTypedPercent(check.getTypedPercent()); } }; } }; /** * Checks possible execution paths of the program for problems: missing return * statements and dead code. */ private final PassFactory checkControlFlow = new PassFactory("checkControlFlow", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { List<Callback> callbacks = Lists.newArrayList(); if (options.checkUnreachableCode.isOn()) { callbacks.add( new CheckUnreachableCode(compiler, options.checkUnreachableCode)); } if (options.checkMissingReturn.isOn() && options.checkTypes) { callbacks.add( new CheckMissingReturn(compiler, options.checkMissingReturn)); } return combineChecks(compiler, callbacks); } }; /** Checks access controls. Depends on type-inference. */ private final PassFactory checkAccessControls = new PassFactory("checkAccessControls", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckAccessControls(compiler); } }; /** Executes the given callbacks with a {@link CombinedCompilerPass}. */ private static CompilerPass combineChecks(AbstractCompiler compiler, List<Callback> callbacks) { Preconditions.checkArgument(callbacks.size() > 0); Callback[] array = callbacks.toArray(new Callback[callbacks.size()]); return new CombinedCompilerPass(compiler, array); } /** A compiler pass that resolves types in the global scope. */ private class GlobalTypeResolver implements CompilerPass { private final AbstractCompiler compiler; GlobalTypeResolver(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { if (topScope == null) { regenerateGlobalTypedScope(compiler, root.getParent()); } else { compiler.getTypeRegistry().resolveTypesInScope(topScope); } } } /** Checks global name usage. */ private final PassFactory checkGlobalNames = new PassFactory("Check names", true) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>COMPILED_CONSTANT_NAME, new Node(Token.TRUE)); } if (options.closurePass && options.locale != null) { additionalReplacements.put(CLOSURE_LOCALE_CONSTANT_NAME, Node.newString(options.locale)); } return additionalReplacements; } private final PassFactory printNameReferenceGraph = new PassFactory("printNameReferenceGraph", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { NameReferenceGraphConstruction gc = new NameReferenceGraphConstruction(compiler); gc.process(externs, jsRoot); String graphFileName = options.nameReferenceGraphPath; try { Files.write(DotFormatter.toDot(gc.getNameReferenceGraph()), new File(graphFileName), Charsets.UTF_8); } catch (IOException e) { compiler.report( JSError.make( NAME_REF_GRAPH_FILE_ERROR, e.getMessage(), graphFileName)); } } }; } }; private final PassFactory printNameReferenceReport = new PassFactory("printNameReferenceReport", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { NameReferenceGraphConstruction gc = new NameReferenceGraphConstruction(compiler); String reportFileName = options.nameReferenceReportPath; try { NameReferenceGraphReport report = new NameReferenceGraphReport(gc.getNameReferenceGraph()); Files.write(report.getHtmlReport(), new File(reportFileName), Charsets.UTF_8); } catch (IOException e) { compiler.report( JSError.make( NAME_REF_REPORT_FILE_ERROR, e.getMessage(), reportFileName)); } } }; } }; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * A builder for the Rhino Node representing Function parameters. * @author nicksantos@google.com (Nick Santos) */ public class FunctionParamBuilder { private final JSTypeRegistry registry; private final Node root = new Node(Token.LP); public FunctionParamBuilder(JSTypeRegistry registry) { this.registry = registry; } /** * Add parameters of the given type to the end of the param list. * @return False if this is called after optional params are added. */ public boolean addRequiredParams(JSType ...types) { if (hasOptionalOrVarArgs()) { return false; } for (JSType type : types) { newParameter(type); } return true; } /** * Add optional parameters of the given type to the end of the param list. * @param types Types for each optional parameter. The builder will make them * undefineable. * @return False if this is called after var

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> args are added. */ public boolean addOptionalParams(JSType ...types) { if (hasVarArgs()) { return false; } for (JSType type : types) { newParameter(registry.createOptionalType(type)).setOptionalArg(true); } return true; } /** * Add variable arguments to the end of the parameter list. * @return False if this is called after var args are added. */ public boolean addVarArgs(JSType type) { if (hasVarArgs()) { return false; } // There are two types of variable argument functions: // 1) Programmer-defined var args // 2) Native bottom types that can accept any argument. // For the first one, "undefined" is a valid value for all arguments. // For the second, we do not want to cast it up to undefined. if (!type.isEmptyType()) { type = registry.createOptionalType(type); } newParameter(type).setVarArgs(true); return true; } /** * Copies the parameter specification from the given node. */ public Node newParameterFromNode(Node n) { Node newParam = newParameter(n.getJSType()); newParam.setVarArgs(n.isVarArgs()); newParam.setOptionalArg(n.isOptionalArg()); return newParam; } // Add a parameter to the list with the given type. private Node newParameter(JSType type) { Node paramNode = Node.newString(Token.NAME, ""); paramNode.setJSType(type); root.addChildToBack(paramNode); return paramNode; } public Node build() { return root; } private boolean hasOptionalOrVarArgs() { Node lastChild = root.getLastChild(); return lastChild != null && (lastChild.isOptionalArg() || lastChild.isVarArgs()); } public boolean hasVarArgs() { Node lastChild = root.getLastChild(); return lastChild != null && lastChild.isVarArgs(); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderInequality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public TypePair getTypesUnderShallowInequality(JSType that) { UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry); UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry); for (JSType element : alternates) { TypePair p = element.getTypesUnderShallowInequality(that); if (p.typeA != null) { thisRestricted.addAlternate(p.typeA); } if (p.typeB != null) { thatRestricted.addAlternate(p.typeB); } } return new TypePair( thisRestricted.build(), thatRestricted.build()); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseUnionType(this); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); // for circularly defined types. boolean changed = false; ImmutableList.Builder<JSType> resolvedTypes = ImmutableList.builder(); for (JSType alternate : alternates) { JSType newAlternate = alternate.resolve(t, scope); changed |= (alternate != newAlternate); resolvedTypes.add(alternate); } if (changed) { Collection<JSType> newAlternates = resolvedTypes.build(); Preconditions.checkState( newAlternates.hashCode() == this.hashcode); alternates = newAlternates; } return this; } @Override public String toDebugHashCodeString() { List<String> hashCodes = Lists.newArrayList(); for (JSType a : alternates) { hashCodes.add(a.toDebugHashCodeString()); } return "{(" + Joiner.on(",").join(hashCodes) + ")}"; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Builder.recordJavaDispatch()) { parser.addWarning("msg.jsdoc.javadispatch", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case EXTENDS: case IMPLEMENTS: skipEOLs(); token = next(); lineno = stream.getLineno(); charno = stream.getCharno(); boolean matchingRc = false; if (token == JsDocToken.LC) { token = next(); matchingRc = true; } if (token == JsDocToken.STRING) { Node typeNode = parseAndRecordTypeNameNode( token, lineno, charno, matchingRc); lineno = stream.getLineno(); charno = stream.getCharno(); typeNode = wrapNode(Token.BANG, typeNode); if (typeNode != null && !matchingRc) { typeNode.putBooleanProp(Node.BRACELESS_TYPE, true); } type = createJSTypeExpression(typeNode); if (annotation == Annotation.EXTENDS) { if (!jsdocBuilder.recordBaseType(type)) { parser.addWarning( "msg.jsdoc.incompat.type", lineno, charno); } } else { Preconditions.checkState( annotation == Annotation.IMPLEMENTS); if (!jsdocBuilder.recordImplementedInterface(type)) { parser.addWarning("msg.jsdoc.implements.duplicate", lineno, charno); } } token = next(); if (matchingRc) { if (token != JsDocToken.RC) { parser.addWarning("msg.jsdoc.missing.rc", stream.getLineno(), stream.getCharno()); } } else if (token != JsDocToken.EOL && token != JsDocToken.EOF && token != JsDocToken.EOC) { parser.addWarning("msg.end.annotation.expected", stream.getLineno(), stream.getCharno()); } } else { parser.addWarning("msg.no.type.name", lineno, charno); } token = eatTokensUntilEOL(token); continue retry; case HIDDEN: if (!jsdocBuilder.recordHiddenness()) { parser.addWarning("msg.jsdoc.hidden", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case LENDS: skipEOLs(); matchingRc = false; if (match(JsDocToken.LC)) { token = next(); matchingRc = true; } if (match(JsDocToken.STRING)) { token = next(); if (!jsdocBuilder.recordLends(stream.getString())) { parser.addWarning("msg.jsdoc.lends.incompatible", stream.getLineno(), stream.getCharno()); } } else { parser.addWarning("

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>TypeName( templateTypeName)) { parser.addWarning("msg.jsdoc.template.at.most.once", stream.getLineno(), stream.getCharno()); } token = templateInfo.token; continue retry; case VERSION: ExtractionInfo versionInfo = extractSingleLineBlock(); String version = versionInfo.string; if (version.length() == 0) { parser.addWarning("msg.jsdoc.versionmissing", stream.getLineno(), stream.getCharno()); } else { if (!jsdocBuilder.recordVersion(version)) { parser.addWarning("msg.jsdoc.extraversion", stream.getLineno(), stream.getCharno()); } } token = versionInfo.token; continue retry; case DEFINE: case RETURN: case THIS: case TYPE: case TYPEDEF: skipEOLs(); lineno = stream.getLineno(); charno = stream.getCharno(); token = next(); Node typeNode = parseAndRecordTypeNode(token, lineno, charno); if (annotation == Annotation.THIS) { typeNode = wrapNode(Token.BANG, typeNode); if (typeNode != null && token != JsDocToken.LC) { typeNode.putBooleanProp(Node.BRACELESS_TYPE, true); } } type = createJSTypeExpression(typeNode); if (type == null) { // error reported during recursive descent // recovering parsing } else { switch (annotation) { case DEFINE: if (!jsdocBuilder.recordDefineType(type)) { parser.addWarning("msg.jsdoc.define", lineno, charno); } break; case RETURN: if (!jsdocBuilder.recordReturnType(type)) { parser.addWarning( "msg.jsdoc.incompat.type", lineno, charno); break; } // *Update* the token to that after the type annotation. token = current(); // Find the return's description (if applicable). if (jsdocBuilder.shouldParseDocumentation()) { ExtractionInfo returnDescriptionInfo = extractMultilineTextualBlock(token); String returnDescription = returnDescriptionInfo.string; if (returnDescription.length() > 0) { jsdocBuilder.recordReturnDescription( returnDescription); } token = returnDescriptionInfo.token; } else { token = eatTokensUntilEOL(token); } continue retry; case THIS: if (!jsdocBuilder.recordThisType(type)) { parser.addWarning( "msg.jsdoc.incompat.type", lineno, charno); } break; case TYPE: if (!jsdocBuilder.recordType(type)) { parser.addWarning( "msg.jsdoc.incompat.type", lineno, charno); } break; case TYPEDEF: if (!jsdocBuilder.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> it. Note that this method consumes input. * * @param token The current token. * @param lineno The line of the type expression. * @param startCharno The starting character position of the type expression. * @param matchingLC Whether the type expression starts with a "{". * @return The type expression found or null if none. */ private Node parseAndRecordTypeNameNode(JsDocToken token, int lineno, int startCharno, boolean matchingLC) { return parseAndRecordTypeNode(token, lineno, startCharno, matchingLC, true); } /** * Looks for a type expression at the current token and if found, * returns it. Note that this method consumes input. * * Parameter type expressions are special for two reasons: * <ol> * <li>They must begin with '{', to distinguish type names from param names. * <li>They may end in '=', to denote optionality. * </ol> * * @param token The current token. * @return The type expression found or null if none. */ private Node parseAndRecordParamTypeNode(JsDocToken token) { Preconditions.checkArgument(token == JsDocToken.LC); int lineno = stream.getLineno(); int startCharno = stream.getCharno(); Node typeNode = parseParamTypeExpressionAnnotation(token); int endCharno = stream.getCharno(); jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno, true); return typeNode; } /** * Looks for a parameter type expression at the current token and if found, * returns it. Note that this method consumes input. * * @param token The current token. * @param lineno The line of the type expression. * @param startCharno The starting character position of the type expression. * @param matchingLC Whether the type expression starts with a "{". * @param onlyParseSimpleNames If true, only simple type names are parsed * (via a call to parseTypeNameAnnotation instead of * parseTypeExpressionAnnotation). * @return The type expression found or null if none. */ private Node parseAndRecordTypeNode(JsDocToken token, int lineno, int startCharno, boolean matchingLC, boolean onlyParseSimpleNames) { Node typeNode = null; if (onlyParseSimpleNames) { typeNode = parseTypeNameAnnotation(token); } else { typeNode = parseTypeExpressionAnnotation(token); } if (typeNode != null && !matchingLC) { typeNode.putBooleanProp(Node.BRACELESS_TYPE, true); } int endCharno = stream.getCharno(); jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno, matchingLC); return typeNode; } /** * Converts a JSDoc token to its string representation. */ private String

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> private Node parseTypeExpressionAnnotation(JsDocToken token) { if (token == JsDocToken.LC) { skipEOLs(); Node typeNode = parseTopLevelTypeExpression(next()); if (typeNode != null) { skipEOLs(); if (!match(JsDocToken.RC)) { reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } else { next(); } } return typeNode; } else { return parseTypeExpression(token); } } /** * ParamTypeExpressionAnnotation := * '{' OptionalParameterType '}' | * '{' TopLevelTypeExpression '}' | * '{' '...' TopLevelTypeExpression '}' * * OptionalParameterType := * TopLevelTypeExpression '=' */ private Node parseParamTypeExpressionAnnotation(JsDocToken token) { Preconditions.checkArgument(token == JsDocToken.LC); skipEOLs(); boolean restArg = false; token = next(); if (token == JsDocToken.ELLIPSIS) { token = next(); if (token == JsDocToken.RC) { // EMPTY represents the UNKNOWN type in the Type AST. return wrapNode(Token.ELLIPSIS, new Node(Token.EMPTY)); } restArg = true; } Node typeNode = parseTopLevelTypeExpression(token); if (typeNode != null) { skipEOLs(); if (restArg) { typeNode = wrapNode(Token.ELLIPSIS, typeNode); } else if (match(JsDocToken.EQUALS)) { next(); skipEOLs(); typeNode = wrapNode(Token.EQUALS, typeNode); } if (!match(JsDocToken.RC)) { reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } else { next(); } } return typeNode; } /** * TypeNameAnnotation := TypeName | '{' TypeName '}' */ private Node parseTypeNameAnnotation(JsDocToken token) { if (token == JsDocToken.LC) { skipEOLs(); Node typeNode = parseTypeName(next()); if (typeNode != null) { skipEOLs(); if (!match(JsDocToken.RC)) { reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } else { next(); } } return typeNode; } else { return parseTypeName(token); } } /** * TopLevelTypeExpression := TypeExpression * | TypeUnionList * * We made this rule up, for the sake of backwards compatibility. */ private Node parseTopLevelTypeExpression(JsDocToken token) { Node typeExpr = parseTypeExpression(token); if (typeExpr != null) { // top-level unions are allowed if (match(JsDocToken.PIPE)) { next(); if (match(JsDocToken.PIPE)) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // We support double pipes for backwards-compatibility. next(); } skipEOLs(); token = next(); return parseUnionTypeWithAlternate(token, typeExpr); } } return typeExpr; } /** * TypeExpressionList := TopLevelTypeExpression * | TopLevelTypeExpression ',' TypeExpressionList */ private Node parseTypeExpressionList(JsDocToken token) { Node typeExpr = parseTopLevelTypeExpression(token); if (typeExpr == null) { return null; } Node typeList = new Node(Token.BLOCK); typeList.addChildToBack(typeExpr); while (match(JsDocToken.COMMA)) { next(); skipEOLs(); typeExpr = parseTopLevelTypeExpression(next()); if (typeExpr == null) { return null; } typeList.addChildToBack(typeExpr); } return typeList; } /** * TypeExpression := BasicTypeExpression * | '?' BasicTypeExpression * | '!' BasicTypeExpression * | BasicTypeExpression '?' * | BasicTypeExpression '!' * | '?' */ private Node parseTypeExpression(JsDocToken token) { if (token == JsDocToken.QMARK) { // A QMARK could mean that a type is nullable, or that it's unknown. // We use look-ahead 1 to determine whether it's unknown. Otherwise, // we assume it means nullable. There are 5 cases: // {?} - right curly // {?=} - equals // {function(?, number)} - comma // {function(number, ?)} - right paren // {function(): ?|number} - pipe // I'm not a big fan of using look-ahead for this, but it makes // the type language a lot nicer. token = next(); if (token == JsDocToken.COMMA || token == JsDocToken.EQUALS || token == JsDocToken.RC || token == JsDocToken.RP || token == JsDocToken.PIPE) { restoreLookAhead(token); return newNode(Token.QMARK); } return wrapNode(Token.QMARK, parseBasicTypeExpression(token)); } else if (token == JsDocToken.BANG) { return wrapNode(Token.BANG, parseBasicTypeExpression(next())); } else { Node basicTypeExpr = parseBasicTypeExpression(token); if (basicTypeExpr != null) { if (match(JsDocToken.QMARK)) { next(); return wrapNode(Token.QMARK, basicTypeExpr); } else if (match(JsDocToken.BANG)) { next(); return wrapNode(Token.BANG, basicTypeExpr); } } return basicTypeExpr; } } /** * BasicTypeExpression := '*' | 'null' | 'undefined' | TypeName * | FunctionType | UnionType |

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> RecordType | ArrayType */ private Node parseBasicTypeExpression(JsDocToken token) { if (token == JsDocToken.STAR) { return newNode(Token.STAR); } else if (token == JsDocToken.LB) { skipEOLs(); return parseArrayType(next()); } else if (token == JsDocToken.LC) { skipEOLs(); return parseRecordType(next()); } else if (token == JsDocToken.LP) { skipEOLs(); return parseUnionType(next()); } else if (token == JsDocToken.STRING) { String string = stream.getString(); if ("function".equals(string)) { skipEOLs(); return parseFunctionType(next()); } else if ("null".equals(string) || "undefined".equals(string)) { return newStringNode(string); } else { return parseTypeName(token); } } return reportGenericTypeSyntaxWarning(); } /** * TypeName := NameExpression | NameExpression TypeApplication * TypeApplication := '.<' TypeExpressionList '>' * TypeExpressionList := TypeExpression // a white lie */ private Node parseTypeName(JsDocToken token) { if (token != JsDocToken.STRING) { return reportGenericTypeSyntaxWarning(); } Node typeName = newStringNode(stream.getString()); if (match(JsDocToken.LT)) { next(); skipEOLs(); Node memberType = parseTypeExpressionList(next()); if (memberType != null) { typeName.addChildToFront(memberType); skipEOLs(); if (!match(JsDocToken.GT)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.gt"); } next(); } } return typeName; } /** * FunctionType := 'function' FunctionSignatureType * FunctionSignatureType := * TypeParameters '(' 'this' ':' TypeName, ParametersType ')' ResultType */ private Node parseFunctionType(JsDocToken token) { // NOTE(nicksantos): We're not implementing generics at the moment, so // just throw out TypeParameters. if (token != JsDocToken.LP) { return reportTypeSyntaxWarning("msg.jsdoc.missing.lp"); } Node functionType = newNode(Token.FUNCTION); Node parameters = null; skipEOLs(); if (!match(JsDocToken.RP)) { token = next(); boolean hasParams = true; if (token == JsDocToken.STRING && "this".equals(stream.getString())) { if (match(JsDocToken.COLON)) { next(); skipEOLs(); Node thisType = wrapNode(Token.THIS, parseTypeName(next())); if (thisType == null) { return null; } functionType.addChildToFront(thisType); } else { return reportTypeSyntaxWarning("msg.jsdoc.missing.colon"); } if (match(Js

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>DocToken.COMMA)) { next(); skipEOLs(); token = next(); } else { hasParams = false; } } if (hasParams) { parameters = parseParametersType(token); if (parameters == null) { return null; } } } if (parameters != null) { functionType.addChildToBack(parameters); } skipEOLs(); if (!match(JsDocToken.RP)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.rp"); } skipEOLs(); Node resultType = parseResultType(next()); if (resultType == null) { return null; } else { functionType.addChildToBack(resultType); } return functionType; } /** * ParametersType := RestParameterType | NonRestParametersType * | NonRestParametersType ',' RestParameterType * RestParameterType := '...' Identifier * NonRestParametersType := ParameterType ',' NonRestParametersType * | ParameterType * | OptionalParametersType * OptionalParametersType := OptionalParameterType * | OptionalParameterType, OptionalParametersType * OptionalParameterType := ParameterType= * ParameterType := TypeExpression | Identifier ':' TypeExpression */ // NOTE(nicksantos): The official ES4 grammar forces optional and rest // arguments to come after the required arguments. Our parser does not // enforce this. Instead we allow them anywhere in the function at parse-time, // and then warn about them during type resolution. // // In theory, it might be mathematically nicer to do the order-checking here. // But in practice, the order-checking for structural functions is exactly // the same as the order-checking for @param annotations. And the latter // has to happen during type resolution. Rather than duplicate the // order-checking in two places, we just do all of it in type resolution. private Node parseParametersType(JsDocToken token) { Node paramsType = newNode(Token.LP); boolean isVarArgs = false; Node paramType = null; if (token != JsDocToken.RP) { do { if (paramType != null) { // skip past the comma next(); skipEOLs(); token = next(); } if (token == JsDocToken.ELLIPSIS) { // In the latest ES4 proposal, there are no type constraints allowed // on variable arguments. We support the old syntax for backwards // compatibility, but we should gradually tear it out. skipEOLs(); if (match(JsDocToken.RP)) { paramType = newNode(Token.ELLIPSIS); } else { skipEOLs(); if (!match(JsDocToken.LB)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.lb"); } next(); skipEOLs(); paramType = wrapNode(Token.ELLIPSIS, parseTypeExpression(next())); skipEOLs(); if (!match

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(JsDocToken.RB)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.rb"); } skipEOLs(); next(); } isVarArgs = true; } else { paramType = parseTypeExpression(token); if (match(JsDocToken.EQUALS)) { skipEOLs(); next(); paramType = wrapNode(Token.EQUALS, paramType); } } if (paramType == null) { return null; } paramsType.addChildToBack(paramType); if (isVarArgs) { break; } } while (match(JsDocToken.COMMA)); } if (isVarArgs && match(JsDocToken.COMMA)) { return reportTypeSyntaxWarning("msg.jsdoc.function.varargs"); } // The right paren will be checked by parseFunctionType return paramsType; } /** * ResultType := <empty> | ':' void | ':' TypeExpression */ private Node parseResultType(JsDocToken token) { skipEOLs(); if (!match(JsDocToken.COLON)) { return newNode(Token.EMPTY); } token = next(); skipEOLs(); if (match(JsDocToken.STRING) && "void".equals(stream.getString())) { next(); return newNode(Token.VOID); } else { return parseTypeExpression(next()); } } /** * UnionType := '(' TypeUnionList ')' * TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList * * We've removed the empty union type. */ private Node parseUnionType(JsDocToken token) { return parseUnionTypeWithAlternate(token, null); } /** * Create a new union type, with an alternate that has already been * parsed. The alternate may be null. */ private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) { Node union = newNode(Token.PIPE); if (alternate != null) { union.addChildToBack(alternate); } Node expr = null; do { if (expr != null) { skipEOLs(); token = next(); Preconditions.checkState( token == JsDocToken.PIPE || token == JsDocToken.COMMA); boolean isPipe = token == JsDocToken.PIPE; if (isPipe && match(JsDocToken.PIPE)) { // We support double pipes for backwards compatiblity. next(); } skipEOLs(); token = next(); } expr = parseTypeExpression(token); if (expr == null) { return null; } union.addChildToBack(expr); // We support commas for backwards compatiblity. } while (match(JsDocToken.PIPE, JsDocToken.COMMA)); if (alternate == null) { skipEOLs(); if (!match(JsDocToken.RP)) { return reportTypeSyntaxWarning

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>("msg.jsdoc.missing.rp"); } next(); } return union; } /** * ArrayType := '[' ElementTypeList ']' * ElementTypeList := <empty> | TypeExpression | '...' TypeExpression * | TypeExpression ',' ElementTypeList */ private Node parseArrayType(JsDocToken token) { Node array = newNode(Token.LB); Node arg = null; boolean hasVarArgs = false; do { if (arg != null) { next(); skipEOLs(); token = next(); } if (token == JsDocToken.ELLIPSIS) { arg = wrapNode(Token.ELLIPSIS, parseTypeExpression(next())); hasVarArgs = true; } else { arg = parseTypeExpression(token); } if (arg == null) { return null; } array.addChildToBack(arg); if (hasVarArgs) { break; } skipEOLs(); } while (match(JsDocToken.COMMA)); if (!match(JsDocToken.RB)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.rb"); } next(); return array; } /** * RecordType := '{' FieldTypeList '}' */ private Node parseRecordType(JsDocToken token) { Node recordType = newNode(Token.LC); Node fieldTypeList = parseFieldTypeList(token); if (fieldTypeList == null) { return reportGenericTypeSyntaxWarning(); } skipEOLs(); if (!match(JsDocToken.RC)) { return reportTypeSyntaxWarning("msg.jsdoc.missing.rc"); } next(); recordType.addChildToBack(fieldTypeList); return recordType; } /** * FieldTypeList := FieldType | FieldType ',' FieldTypeList */ private Node parseFieldTypeList(JsDocToken token) { Node fieldTypeList = newNode(Token.LB); do { Node fieldType = parseFieldType(token); if (fieldType == null) { return null; } fieldTypeList.addChildToBack(fieldType); skipEOLs(); if (!match(JsDocToken.COMMA)) { break; } // Move to the comma token. next(); // Move to the token passed the comma. skipEOLs(); token = next(); } while (true); return fieldTypeList; } /** * FieldType := FieldName | FieldName ':' TypeExpression */ private Node parseFieldType(JsDocToken token) { Node fieldName = parseFieldName(token); if (fieldName == null) { return null; } skipEOLs(); if (!match(JsDocToken.COLON)) { return fieldName; } // Move to the colon. next(); // Move to the token after the colon and parse // the type expression. skipEOLs(); Node typeExpression = parseTypeExpression(next()); if (typeExpression == null) { return null;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } Node fieldType = newNode(Token.COLON); fieldType.addChildToBack(fieldName); fieldType.addChildToBack(typeExpression); return fieldType; } /** * FieldName := NameExpression | StringLiteral | NumberLiteral | * ReservedIdentifier */ private Node parseFieldName(JsDocToken token) { switch (token) { case STRING: String string = stream.getString(); return newStringNode(string); default: return null; } } private Node wrapNode(int type, Node n) { return n == null ? null : new Node(type, n, stream.getLineno(), stream.getCharno()); } private Node newNode(int type) { return new Node(type, stream.getLineno(), stream.getCharno()); } private Node newStringNode(String s) { return Node.newString(s, stream.getLineno(), stream.getCharno()); } private Node reportTypeSyntaxWarning(String warning) { parser.addWarning(warning, stream.getLineno(), stream.getCharno()); return null; } private Node reportGenericTypeSyntaxWarning() { return reportTypeSyntaxWarning("msg.jsdoc.type.syntax"); } /** * Eats tokens until {@link JsDocToken#EOL} included, and switches back the * state to {@link State#SEARCHING_ANNOTATION}. */ private JsDocToken eatTokensUntilEOL() { return eatTokensUntilEOL(next()); } /** * Eats tokens until {@link JsDocToken#EOL} included, and switches back the * state to {@link State#SEARCHING_ANNOTATION}. */ private JsDocToken eatTokensUntilEOL(JsDocToken token) { do { if (token == JsDocToken.EOL || token == JsDocToken.EOC || token == JsDocToken.EOF) { state = State.SEARCHING_ANNOTATION; return token; } token = next(); } while (true); } /** * Specific value indicating that the {@link #unreadToken} contains no token. */ private static final JsDocToken NO_UNREAD_TOKEN = null; /** * One token buffer. */ private JsDocToken unreadToken = NO_UNREAD_TOKEN; /** Restores the lookahead token to the token stream */ private void restoreLookAhead(JsDocToken token) { unreadToken = token; } /** * Tests whether the next symbol of the token stream matches the specific * token. */ private boolean match(JsDocToken token) { unreadToken = next(); return unreadToken == token; } /** * Tests that the next symbol of the token stream matches one of the specified * tokens. */ private boolean match(JsDocToken token1, JsDocToken token2) { unreadToken = next(); return unreadToken == token1 || unreadToken == token2; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; /** * The {@code StaticSlot} interface must be implemented by variables that can * appear as members of a {@code StaticScope}. * * @param <T> The type of information stored about the slot */ public interface StaticSlot<T> { /** * Gets the name of the slot. */ String getName(); /** * Returns the type information, if any, for this slot. * @return The type or {@code null} if no type is declared for it. */ T getType(); /** * Returns whether the type has been inferred (as opposed to declared). */ boolean isTypeInferred(); }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * graph coloring in {@link GraphColoring} to determine which two variables can * be merge together safely. * */ class CoalesceVariableNames extends AbstractPostOrderCallback implements CompilerPass, ScopedCallback { private final AbstractCompiler compiler; private final Deque<GraphColoring<Var, Void>> colorings; private final boolean usePseudoNames; private static final Comparator<Var> coloringTieBreaker = new Comparator<Var>() { public int compare(Var v1, Var v2) { return v1.index - v2.index; } }; /** * @param usePseudoNames For debug purposes, when merging variable foo and bar * to foo, rename both variable to foo_bar. */ CoalesceVariableNames(AbstractCompiler compiler, boolean usePseudoNames) { Preconditions.checkState(!compiler.isNormalized()); this.compiler = compiler; colorings = Lists.newLinkedList(); this.usePseudoNames = usePseudoNames; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void enterScope(NodeTraversal t) { // TODO(user): We CAN do this in the global scope, just need to be // careful when something is exported. Liveness uses bit-vector for live // sets so I don't see compilation time will be a problem for running this // pass in the global scope. if (t.inGlobalScope()) { return; } Scope scope = t.getScope(); ControlFlowGraph<Node> cfg = t.getControlFlowGraph(); LiveVariablesAnalysis liveness = new LiveVariablesAnalysis(cfg, scope, compiler); // If the function has exactly 2 params, mark them as escaped. This is // a work-around for an IE bug where it throws an exception if you // write to the parameters of the callback in a sort(). See: // http://code.google.com/p/closure-compiler/issues/detail?id=58 if (scope.getRootNode().getFirstChild().getNext().getChildCount() == 2) { liveness.markAllParametersEscaped(); } liveness.analyze(); UndiGraph<Var, Void> interferenceGraph = computeVariableNamesInterferenceGraph( t, cfg, liveness.getEscapedLocals()); GraphColoring<Var, Void> coloring = new GreedyGraphColoring<Var, Void>(interferenceGraph, coloringTieBreaker); coloring.color(); colorings.push(coloring); } @Override public void exitScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } colorings.pop(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (colorings.isEmpty() || !NodeUtil.isName(n) || NodeUtil.isFunction(parent

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> why // that is but, for now, we will respect the dead functions and not play // around with it. if (!NodeUtil.isFunction(v.getParentNode())) { interferenceGraph.createNode(v); } } } // Go through each variable and try to connect them. for (Iterator<Var> i1 = scope.getVars(); i1.hasNext();) { Var v1 = i1.next(); NEXT_VAR_PAIR: for (Iterator<Var> i2 = scope.getVars(); i2.hasNext();) { Var v2 = i2.next(); // Skip duplicate pairs. if (v1.index >= v2.index) { continue; } if (!interferenceGraph.hasNode(v1) || !interferenceGraph.hasNode(v2)) { // Skip nodes that were not added. They are globals and escaped // locals. Also avoid merging a variable with itself. continue NEXT_VAR_PAIR; } if (v1.getParentNode().getType() == Token.LP && v2.getParentNode().getType() == Token.LP) { interferenceGraph.connectIfNotFound(v1, null, v2); continue NEXT_VAR_PAIR; } // Go through every CFG node in the program and look at // this variable pair. If they are both live at the same // time, add an edge between them and continue to the next pair. NEXT_CROSS_CFG_NODE: for (DiGraphNode<Node, Branch> cfgNode : cfg.getDirectedGraphNodes()) { if (cfg.isImplicitReturn(cfgNode)) { continue NEXT_CROSS_CFG_NODE; } FlowState<LiveVariableLattice> state = cfgNode.getAnnotation(); // Check the live states and add edge when possible. if ((state.getIn().isLive(v1) && state.getIn().isLive(v2)) || (state.getOut().isLive(v1) && state.getOut().isLive(v2))) { interferenceGraph.connectIfNotFound(v1, null, v2); continue NEXT_VAR_PAIR; } } // v1 and v2 might not have an edge between them! woohoo. there's // one last sanity check that we have to do: we have to check // if there's a collision *within* the cfg node. NEXT_INTRA_CFG_NODE: for (DiGraphNode<Node, Branch> cfgNode : cfg.getDirectedGraphNodes()) { if (cfg.isImplicitReturn(cfgNode)) { continue NEXT_INTRA_CFG_NODE; } FlowState<LiveVariableLattice> state = cfgNode.getAnnotation(); boolean v1OutLive = state.getOut().isLive(v1); boolean v2OutLive = state.getOut().isLive(v2); CombinedLiveRangeChecker checker = new CombinedLiveRangeChecker(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> new LiveRangeChecker(v1, v2OutLive ? null : v2), new LiveRangeChecker(v2, v1OutLive ? null : v1)); NodeTraversal.traverse( compiler, cfgNode.getValue(), checker); if (checker.connectIfCrossed(interferenceGraph)) { continue NEXT_VAR_PAIR; } } } } return interferenceGraph; } /** * A simple wrapper calls to call two AbstractCfgNodeTraversalCallback * callback during the same traversal. Both traversals must have the same * "shouldTraverse" conditions. */ private static class CombinedLiveRangeChecker extends AbstractCfgNodeTraversalCallback { private final LiveRangeChecker callback1; private final LiveRangeChecker callback2; CombinedLiveRangeChecker( LiveRangeChecker callback1, LiveRangeChecker callback2) { this.callback1 = callback1; this.callback2 = callback2; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (LiveRangeChecker.shouldVisit(n)) { callback1.visit(t, n, parent); callback2.visit(t, n, parent); } } boolean connectIfCrossed(UndiGraph<Var, Void> interferenceGraph) { if (callback1.crossed || callback2.crossed) { Var v1 = callback1.getDef(); Var v2 = callback2.getDef(); interferenceGraph.connectIfNotFound(v1, null, v2); return true; } return false; } } /** * Tries to remove variable declaration if the variable has been coalesced * with another variable that has already been declared. */ private void removeVarDeclaration(Node name) { Node var = name.getParent(); Node parent = var.getParent(); // Special case when we are in FOR-IN loop. if (NodeUtil.isForIn(parent)) { var.removeChild(name); parent.replaceChild(var, name); } else if (var.hasOneChild()) { // The removal is easy when there is only one variable in the VAR node. if (name.hasChildren()) { Node value = name.removeFirstChild(); var.removeChild(name); Node assign = new Node(Token.ASSIGN, name, value) .copyInformationFrom(name); // We don't need to wrapped it with EXPR node if it is within a FOR. if (parent.getType() != Token.FOR) { assign = NodeUtil.newExpr(assign); } parent.replaceChild(var, assign); } else { // In a FOR( ; ; ) node, we must replace it with an EMPTY or else it // becomes a FOR-IN node. NodeUtil.removeChild(parent, var); } } else { if (!name.hasChildren()) { var.removeChild(name);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } // We are going to leave duplicated declaration otherwise. } } private static class LiveRangeChecker extends AbstractCfgNodeTraversalCallback { boolean defFound = false; boolean crossed = false; private final Var def; private final Var use; public LiveRangeChecker(Var def, Var use) { this.def = def; this.use = use; } Var getDef() { return def; } /** * @return Whether any LiveRangeChecker would be interested in the node. */ public static boolean shouldVisit(Node n) { return (NodeUtil.isName(n) || (n.hasChildren() && NodeUtil.isName(n.getFirstChild()))); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!defFound && isAssignTo(def, n, parent)) { defFound = true; } if (defFound && (use == null || isReadFrom(use, n))) { crossed = true; } } private static boolean isAssignTo(Var var, Node n, Node parent) { if (NodeUtil.isName(n) && var.getName().equals(n.getString()) && parent != null) { if (parent.getType() == Token.LP) { // In a function declaration, the formal parameters are assigned. return true; } else if (NodeUtil.isVar(parent)) { // If this is a VAR declaration, if the name node has a child, we are // assigning to that name. return n.hasChildren(); } return false; // Definitely a read. } else { // Lastly, any assignmentOP is also an assign. Node name = n.getFirstChild(); return name != null && NodeUtil.isName(name) && var.getName().equals(name.getString()) && NodeUtil.isAssignmentOp(n); } } private static boolean isReadFrom(Var var, Node name) { return name != null && NodeUtil.isName(name) && var.getName().equals(name.getString()) && !NodeUtil.isLhs(name, name.getParent()); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * * @param n1 Node one. * @param n2 Node two. * @return The list of edges between those two values in the graph. */ public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2); /** * Checks whether the node exists in the graph ({@link #createNode(Object)} * has been called with that value). * * @param n Node. * @return <code>true</code> if it exist. */ public final boolean hasNode(N n) { return getNode(n) != null; } /** * Checks whether two nodes in the graph are connected. * * @param n1 Node 1. * @param n2 Node 2. * @return <code>true</code> if the two nodes are connected. */ public abstract boolean isConnected(N n1, N n2); /** * Checks whether two nodes in the graph are connected by the given * edge type. * * @param n1 Node 1. * @param e The edge type. * @param n2 Node 2. */ public abstract boolean isConnected(N n1, E e, N n2); /** * Gets the node of the specified type, or throws an * IllegalArgumentException. */ @SuppressWarnings("unchecked") <T extends GraphNode<N, E>> T getNodeOrFail(N val) { T node = (T) getNode(val); if (node == null) { throw new IllegalArgumentException(val + " does not exist in graph"); } return node; } public final void clearNodeAnnotations() { for (GraphNode<N, E> n : getNodes()) { n.setAnnotation(null); } } /** Makes each edge's annotation null. */ public final void clearEdgeAnnotations() { for (GraphEdge<N, E> e : getEdges()) { e.setAnnotation(null); } } /** * Pushes nodes' annotation values. Restored with * {@link #popNodeAnnotations()}. Nodes' annotation values are cleared. */ public final void pushNodeAnnotations() { if (nodeAnnotationStack == null) { nodeAnnotationStack = Lists.newLinkedList(); } pushAnnotations(nodeAnnotationStack, getNodes()); } /** * Restores nodes' annotation values to state before last * {@link #pushNodeAnnotations()}. */ public final void popNodeAnnotations() { Preconditions.checkNotNull(nodeAnnotationStack, "Popping node annotations without pushing."); popAnnotations(nodeAnnotationStack); } /** * Pushes edges' annotation values. Restored with * {@link #popEdgeAnnotations()}. Edges' annotation values are cleared. */ public final void pushEdgeAnnotations() { if (edgeAnnotationStack == null) { edgeAnnotationStack = Lists.newLinkedList(); } pushAnnotations(edgeAnnotationStack, getEdges()); } /**

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * Restores edges' annotation values to state before last * {@link #pushEdgeAnnotations()}. */ public final void popEdgeAnnotations() { Preconditions.checkNotNull(edgeAnnotationStack, "Popping edge annotations without pushing."); popAnnotations(edgeAnnotationStack); } /** * A generic edge. * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public interface GraphEdge<N, E> extends Annotatable { /** * Retrieves the edge's value. * * @return The value. */ E getValue(); GraphNode<N, E> getNodeA(); GraphNode<N, E> getNodeB(); } /** * A simple implementation of SubGraph that calculates adjacency by iterating * over a node's neighbors. */ class SimpleSubGraph<N, E> implements SubGraph<N, E> { private Graph<N, E> graph; private List<GraphNode<N, E>> nodes = Lists.newArrayList(); SimpleSubGraph(Graph<N, E> graph) { this.graph = graph; } public boolean isIndependentOf(N value) { GraphNode<N, E> node = graph.getNode(value); for (GraphNode<N, E> n : nodes) { if (graph.getNeighborNodes(n.getValue()).contains(node)) { return false; } } return true; } public void addNode(N value) { nodes.add(graph.getNodeOrFail(value)); } } /** * Pushes a new list on stack and stores nodes annotations in the new list. * Clears objects' annotations as well. */ private static void pushAnnotations( Deque<GraphAnnotationState> stack, Collection<? extends Annotatable> haveAnnotations) { stack.push(new GraphAnnotationState(haveAnnotations.size())); for (Annotatable h : haveAnnotations) { stack.peek().add(new AnnotationState(h, h.getAnnotation())); h.setAnnotation(null); } } /** * Restores the node annotations on the top of stack and pops stack. */ private static void popAnnotations(Deque<GraphAnnotationState> stack) { for (AnnotationState as : stack.pop()) { as.first.setAnnotation(as.second); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Predicate; import com.google.javascript.jscomp.ControlFlowGraph.Branch; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.graph.GraphNode; import com.google.javascript.jscomp.graph.GraphReachability; import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; /** * Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user * about unreachable code. * */ class CheckUnreachableCode implements ScopedCallback { static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error( "JSC_UNREACHABLE_CODE", "unreachable code"); private final AbstractCompiler compiler; private final CheckLevel level; CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) { this.compiler = compiler; this.level = level; } @Override public void enterScope(NodeTraversal t) { initScope(t.getControlFlowGraph()); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n); if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) { // Only report error when there are some line number informations. // There are synthetic nodes with no line number informations, nodes // introduce by other passes (although not likely since this pass should // be executed early) or some rhino bug. if (n.getLineno() != -1 && // Allow spurious semi-colons and spurious breaks. n.getType() != Token.EMPTY && n.getType() != Token.BREAK) { compiler.report(t.makeError(n, level, UNREACHABLE_CODE)); // From now on, we are going to assume the user fixed the error and not // give more warning related to code section reachable from this node.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, Node externsRoot, Node root) { this.compiler = compiler; this.externsRoot = externsRoot; this.root = root; } /** * Gets a list of the roots of the forest of the global names, where the * roots are the top-level names. */ List<Name> getNameForest() { if (!generated) { process(); } return globalNames; } /** * Gets an index of all the global names, indexed by full qualified name * (as in "a", "a.b.c", etc.). */ Map<String, Name> getNameIndex() { if (!generated) { process(); } return nameMap; } /** * If the client adds new nodes to the AST, scan these new nodes * to see if they've added any references to the global namespace. * @param scope The scope to scan. * @param newNodes New nodes to check. */ void scanNewNodes(Scope scope, Set<Node> newNodes) { NodeTraversal t = new NodeTraversal(compiler, new BuildGlobalNamespace(new NodeFilter(newNodes))); t.traverseAtScope(scope); } /** * A filter that looks for qualified names that contain one of the nodes * in the given set. */ private static class NodeFilter implements Predicate<Node> { private final Set<Node> newNodes; NodeFilter(Set<Node> newNodes) { this.newNodes = newNodes; } public boolean apply(Node n) { if (!n.isQualifiedName()) { return false; } Node current; for (current = n; current.getType() == Token.GETPROP; current = current.getFirstChild()) { if (newNodes.contains(current)) { return true; } } return current.getType() == Token.NAME && newNodes.contains(current); } } /** * Builds the namespace lazily. */ private void process() { if (externsRoot != null) { inExterns = true; NodeTraversal.traverse(compiler, externsRoot, new BuildGlobalNamespace()); } inExterns = false; NodeTraversal.traverse(compiler, root, new BuildGlobalNamespace()); generated = true; } /** * Determines whether a name reference in a particular scope is a global name * reference. * * @param name A variable or property name (e.g. "a" or "a.b.c.d") * @param s The scope in which the name is referenced * @return Whether the name reference is a global name reference */ private boolean isGlobalNameReference(String name, Scope s) { String topVarName = getTopVarName(name); return isGlobalVarReference(topVarName, s); } /** * Gets the top variable name from a possibly namespaced name. * * @param name A variable or qualified

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> property name (e.g. "a" or "a.b.c.d") * @return The top variable name (e.g. "a") */ private String getTopVarName(String name) { int firstDotIndex = name.indexOf('.'); return firstDotIndex == -1 ? name : name.substring(0, firstDotIndex); } /** * Determines whether a variable name reference in a particular scope is a * global variable reference. * * @param name A variable name (e.g. "a") * @param s The scope in which the name is referenced * @return Whether the name reference is a global variable reference */ private boolean isGlobalVarReference(String name, Scope s) { Scope.Var v = s.getVar(name); if (v == null && externsScope != null) { v = externsScope.getVar(name); } return v != null && !v.isLocal(); } /** * Gets whether a scope is the global scope. * * @param s A scope * @return Whether the scope is the global scope */ private boolean isGlobalScope(Scope s) { return s.getParent() == null; } // ------------------------------------------------------------------------- /** * Builds a tree representation of the global namespace. Omits prototypes. */ private class BuildGlobalNamespace extends AbstractPostOrderCallback { private final Predicate<Node> nodeFilter; BuildGlobalNamespace() { this(null); } /** * Builds a global namepsace, but only visits nodes that match the * given filter. */ BuildGlobalNamespace(Predicate<Node> nodeFilter) { this.nodeFilter = nodeFilter; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (nodeFilter != null && !nodeFilter.apply(n)) { return; } // If we are traversing the externs, then we save a pointer to the scope // generated by them, so that we can do lookups in it later. if (externsRoot != null && n == externsRoot) { externsScope = t.getScope(); } String name; boolean isSet = false; Name.Type type = Name.Type.OTHER; boolean isPropAssign = false; switch (n.getType()) { case Token.GET: case Token.SET: case Token.STRING: // This may be a key in an object literal declaration. name = null; if (parent != null && parent.getType() == Token.OBJECTLIT) { name = getNameForObjLitKey(n); } if (name == null) return; isSet = true; switch (n.getType()) { case Token.STRING: type = getValueType(n.getFirstChild()); break; case Token.GET: type = Name.Type.GET; break; case Token.SET: type = Name.Type.SET;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> break; default: throw new IllegalStateException("unexpected:" + n); } break; case Token.NAME: // This may be a variable get or set. if (parent != null) { switch (parent.getType()) { case Token.VAR: isSet = true; Node rvalue = n.getFirstChild(); type = rvalue == null ? Name.Type.OTHER : getValueType(rvalue); break; case Token.ASSIGN: if (parent.getFirstChild() == n) { isSet = true; type = getValueType(n.getNext()); } break; case Token.GETPROP: return; case Token.FUNCTION: Node gramps = parent.getParent(); if (gramps == null || NodeUtil.isFunctionExpression(parent)) return; isSet = true; type = Name.Type.FUNCTION; break; } } name = n.getString(); break; case Token.GETPROP: // This may be a namespaced name get or set. if (parent != null) { switch (parent.getType()) { case Token.ASSIGN: if (parent.getFirstChild() == n) { isSet = true; type = getValueType(n.getNext()); isPropAssign = true; } break; case Token.GETPROP: return; } } name = n.getQualifiedName(); if (name == null) return; break; default: return; } // We are only interested in global names. Scope scope = t.getScope(); if (!isGlobalNameReference(name, scope)) { return; } if (isSet) { if (isGlobalScope(scope)) { handleSetFromGlobal(t, n, parent, name, isPropAssign, type); } else { handleSetFromLocal(t, n, parent, name); } } else { handleGet(t, n, parent, name); } } /** * Gets the fully qualified name corresponding to an object literal key, * as long as it and its prefix property names are valid JavaScript * identifiers. The object literal may be nested inside of other object * literals. * * For example, if called with node {@code n} representing "z" in any of * the following expressions, the result would be "w.x.y.z": * <code> var w = {x: {y: {z: 0}}}; </code> * <code> w.x = {y: {z: 0}}; </code> * <code> w.x.y = {'a': 0, 'z': 0}; </code> * * @param n A child of an OBJLIT node * @return The global name, or null if {@code n} doesn't correspond to the * key of an object literal that can be named */ String getNameForObjLitKey(Node n) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Node parent = n.getParent(); Preconditions.checkState(parent.getType() == Token.OBJECTLIT); Node gramps = parent.getParent(); if (gramps == null) { return null; } Node greatGramps = gramps.getParent(); String name; switch (gramps.getType()) { case Token.NAME: // VAR // NAME (gramps) // OBJLIT (parent) // STRING (n) if (greatGramps == null || greatGramps.getType() != Token.VAR) { return null; } name = gramps.getString(); break; case Token.ASSIGN: // ASSIGN (gramps) // NAME|GETPROP // OBJLIT (parent) // STRING (n) Node lvalue = gramps.getFirstChild(); name = lvalue.getQualifiedName(); break; case Token.STRING: // OBJLIT // STRING (gramps) // OBJLIT (parent) // STRING (n) if (greatGramps != null && greatGramps.getType() == Token.OBJECTLIT) { name = getNameForObjLitKey(gramps); } else { return null; } break; default: return null; } if (name != null) { String key = n.getString(); if (TokenStream.isJSIdentifier(key)) { return name + '.' + key; } } return null; } /** * Gets the type of a value or simple expression. * * @param n An rvalue in an assignment or variable declaration (not null) * @return A {@link Name.Type} */ Name.Type getValueType(Node n) { switch (n.getType()) { case Token.OBJECTLIT: return Name.Type.OBJECTLIT; case Token.FUNCTION: return Name.Type.FUNCTION; case Token.OR: // Recurse on the second value. If the first value were an object // literal or function, then the OR would be meaningless and the // second value would be dead code. Assume that if the second value // is an object literal or function, then the first value will also // evaluate to one when it doesn't evaluate to false. return getValueType(n.getLastChild()); case Token.HOOK: // The same line of reasoning used for the OR case applies here. Node second = n.getFirstChild().getNext(); Name.Type t = getValueType(second); if (t != Name.Type.OTHER) return t; Node third = second.getNext(); return getValueType(third); } return Name.Type.OTHER; } /** * Updates our respresentation of the global namespace to reflect an * assignment to a global name in global scope. * * @param t The traversal * @

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>param n The node currently being visited * @param parent {@code n}'s parent * @param name The global name (e.g. "a" or "a.b.c.d") * @param isPropAssign Whether this set corresponds to a property * assignment of the form <code>a.b.c = ...;</code> * @param type The type of the value that the name is being assigned */ void handleSetFromGlobal(NodeTraversal t, Node n, Node parent, String name, boolean isPropAssign, Name.Type type) { if (maybeHandlePrototypePrefix(t, n, parent, name)) return; Name nameObj = getOrCreateName(name); nameObj.type = type; Ref set = new Ref(t, n, Ref.Type.SET_FROM_GLOBAL); nameObj.addRef(set); if (isNestedAssign(parent)) { // This assignment is both a set and a get that creates an alias. Ref get = new Ref(t, n, Ref.Type.ALIASING_GET); nameObj.addRef(get); Ref.markTwins(set, get); } else if (isConstructorOrEnumDeclaration(n, parent)) { // Names with a @constructor or @enum annotation are always collapsed nameObj.setIsClassOrEnum(); } } /** * Determines whether a set operation is a constructor or enumeration * declaration. The set operation may either be an assignment to a name, * a variable declaration, or an object literal key mapping. * * @param n The node that represents the name being set * @param parent Parent node of {@code n} (an ASSIGN, VAR, or OBJLIT node) * @return Whether the set operation is either a constructor or enum * declaration */ private boolean isConstructorOrEnumDeclaration(Node n, Node parent) { // NOTE(nicksantos): This does not handle named constructors // function a() {} // For legacy reasons, we should not fix this, because we do not // know who's depending on the current behavior. JSDocInfo info; int valueNodeType; switch (parent.getType()) { case Token.ASSIGN: info = parent.getJSDocInfo(); valueNodeType = n.getNext().getType(); break; case Token.VAR: info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } Node valueNode = n.getFirstChild(); valueNodeType = valueNode != null ? valueNode.getType() : Token.VOID; break; default: return false; } // Heed the annotations only if they're sensibly used. return info != null && (info.isConstructor() && valueNodeType == Token.FUNCTION || info.hasEnumParameterType() && valueNodeType == Token.OBJECTLIT); } /** * Updates our respresentation of the global namespace to reflect an

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * assignment to a global name in a local scope. * * @param t The traversal * @param n The node currently being visited * @param parent {@code n}'s parent * @param name The global name (e.g. "a" or "a.b.c.d") */ void handleSetFromLocal(NodeTraversal t, Node n, Node parent, String name) { if (maybeHandlePrototypePrefix(t, n, parent, name)) return; Name node = getOrCreateName(name); Ref set = new Ref(t, n, Ref.Type.SET_FROM_LOCAL); node.addRef(set); if (isNestedAssign(parent)) { // This assignment is both a set and a get that creates an alias. Ref get = new Ref(t, n, Ref.Type.ALIASING_GET); node.addRef(get); Ref.markTwins(set, get); } } /** * Updates our respresentation of the global namespace to reflect a read * of a global name. * * @param t The traversal * @param n The node currently being visited * @param parent {@code n}'s parent * @param name The global name (e.g. "a" or "a.b.c.d") */ void handleGet(NodeTraversal t, Node n, Node parent, String name) { if (maybeHandlePrototypePrefix(t, n, parent, name)) return; Ref.Type type = Ref.Type.DIRECT_GET; if (parent != null) { switch (parent.getType()) { case Token.IF: case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: break; case Token.CALL: type = n == parent.getFirstChild() ? Ref.Type.CALL_GET : Ref.Type.ALIASING_GET; break; case Token.NEW: type = n == parent.getFirstChild() ? Ref.Type.DIRECT_GET : Ref.Type.ALIASING_GET; break; case Token.OR: case Token.AND: // This node is x or y in (x||y) or (x&&y). We only know that an // alias is not getting created for this name if the result is used // in a boolean context or assigned to the same name // (e.g. var a = a || {}). type = determineGetTypeForHookOrBooleanExpr(t, parent, name); break; case Token.HOOK: if (n != parent.getFirstChild()) { // This node is y or z in (x?y:z). We only know that an alias is // not getting created for this name if the result is assigned to // the same name (e.g. var a = a ? a : {}). type = determineGetTypeForHookOr

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>BooleanExpr(t, parent, name); } break; default: type = Ref.Type.ALIASING_GET; break; } } handleGet(t, n, parent, name, type); } /** * Determines whether the result of a hook (x?y:z) or boolean expression * (x||y) or (x&&y) is assigned to a specific global name. * * @param t The traversal * @param parent The parent of the current node in the traversal. This node * should already be known to be a HOOK, AND, or OR node. * @param name A name that is already known to be global in the current * scope (e.g. "a" or "a.b.c.d") * @return The expression's get type, either {@link Ref.Type#DIRECT_GET} or * {@link Ref.Type#ALIASING_GET} */ Ref.Type determineGetTypeForHookOrBooleanExpr( NodeTraversal t, Node parent, String name) { Node prev = parent; for (Node anc : parent.getAncestors()) { switch (anc.getType()) { case Token.EXPR_RESULT: case Token.VAR: case Token.IF: case Token.WHILE: case Token.FOR: case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: return Ref.Type.DIRECT_GET; case Token.HOOK: if (anc.getFirstChild() == prev) { return Ref.Type.DIRECT_GET; } break; case Token.ASSIGN: if (!name.equals(anc.getFirstChild().getQualifiedName())) { return Ref.Type.ALIASING_GET; } break; case Token.NAME: // a variable declaration if (!name.equals(anc.getString())) { return Ref.Type.ALIASING_GET; } break; case Token.CALL: if (anc.getFirstChild() != prev) { return Ref.Type.ALIASING_GET; } break; } prev = anc; } return Ref.Type.ALIASING_GET; } /** * Updates our respresentation of the global namespace to reflect a read * of a global name. * * @param t The current node traversal * @param n The node currently being visited * @param parent {@code n}'s parent * @param name The global name (e.g. "a" or "a.b.c.d") * @param type The reference type */ void handleGet(NodeTraversal t, Node n, Node parent, String name, Ref.Type type) { Name node = getOrCreateName(name); // No need to look up additional ancestors, since they won't be used. node.addRef(new Ref(t

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, n, type)); } /** * Updates our respresentation of the global namespace to reflect a read * of a global name's longest prefix before the "prototype" property if the * name includes the "prototype" property. Does nothing otherwise. * * @param t The current node traversal * @param n The node currently being visited * @param parent {@code n}'s parent * @param name The global name (e.g. "a" or "a.b.c.d") * @return Whether the name was handled */ boolean maybeHandlePrototypePrefix(NodeTraversal t, Node n, Node parent, String name) { // We use a string-based approach instead of inspecting the parse tree // to avoid complexities with object literals, possibly nested, beneath // assignments. int numLevelsToRemove; String prefix; if (name.endsWith(".prototype")) { numLevelsToRemove = 1; prefix = name.substring(0, name.length() - 10); } else { int i = name.indexOf(".prototype."); if (i == -1) { return false; } prefix = name.substring(0, i); numLevelsToRemove = 2; i = name.indexOf('.', i + 11); while (i >= 0) { numLevelsToRemove++; i = name.indexOf('.', i + 1); } } if (parent != null && NodeUtil.isObjectLitKey(n, parent)) { // Object literal keys have no prefix that's referenced directly per // key, so we're done. return true; } for (int i = 0; i < numLevelsToRemove; i++) { parent = n; n = n.getFirstChild(); } handleGet(t, n, parent, prefix, Ref.Type.PROTOTYPE_GET); return true; } /** * Determines whether an assignment is nested (i.e. whether its return * value is used). * * @param parent The parent of the current traversal node (not null) * @return Whether it appears that the return value of the assignment is * used */ boolean isNestedAssign(Node parent) { return parent.getType() == Token.ASSIGN && !NodeUtil.isExpressionNode(parent.getParent()); } /** * Gets a {@link Name} instance for a global name. Creates it if necessary, * as well as instances for any of its prefixes that are not yet defined. * * @param name A global name (e.g. "a", "a.b.c.d") * @return The {@link Name} instance for {@code name} */ Name getOrCreateName(String name) { Name node = nameMap.get(name); if (node == null) { int i = name.lastIndexOf('.'); if (i >= 0) { String parentName = name.substring(0, i); Name parent

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> declaration = maybeNewDecl; refs.remove(declaration); break; } } } } switch (ref.type) { case SET_FROM_GLOBAL: globalSets--; break; case SET_FROM_LOCAL: localSets--; break; case PROTOTYPE_GET: case DIRECT_GET: totalGets--; break; case ALIASING_GET: aliasingGets--; totalGets--; break; case CALL_GET: callGets--; totalGets--; break; default: throw new IllegalStateException(); } } } void addRefInternal(Ref ref) { if (refs == null) { refs = new LinkedList<Ref>(); } refs.add(ref); } boolean canEliminate() { if (!canCollapseUnannotatedChildNames() || totalGets > 0) { return false; } if (props != null) { for (Name n : props) { if (!n.canCollapse()) { return false; } } } return true; } boolean canCollapse() { return !inExterns && !isGetOrSetDefinition() && (isClassOrEnum || (parent == null || parent.canCollapseUnannotatedChildNames()) && (globalSets > 0 || localSets > 0)); } boolean isGetOrSetDefinition() { return this.type == Type.GET || this.type == Type.SET; } boolean canCollapseUnannotatedChildNames() { if (type == Type.OTHER || isGetOrSetDefinition() || globalSets != 1 || localSets != 0) { return false; } // Don't try to collapse if the one global set is a twin reference. // We could theoretically handle this case in CollapseProperties, but // it's probably not worth the effort. Preconditions.checkNotNull(declaration); if (declaration.getTwin() != null) { return false; } if (isClassOrEnum) { return true; } // If this is a key of an aliased object literal, then it will be aliased // later. So we won't be able to collapse its properties. if (parent != null && parent.shouldKeepKeys()) { return false; } // If this is aliased, then its properties can't be collapsed either. if (aliasingGets > 0) { return false; } return (parent == null || parent.canCollapseUnannotatedChildNames()); } /** Whether this is an object literal that needs to keep its keys. */ boolean shouldKeepKeys() { return type == Type.OBJECTLIT && aliasingGets > 0; } boolean needsToBeStubbed() { return globalSets == 0 && localSets > 0; } void setIsClassOrEnum() { isClassOrEnum = true; for (Name ancestor = parent; ancestor != null; ancestor = ancestor.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>parent) { ancestor.hasClassOrEnumDescendant = true; } } /** * Determines whether this name is a prefix of at least one class or enum * name. Because classes and enums are always collapsed, the namespace will * have different properties in compiled code than in uncompiled code. * * For example, if foo.bar.DomHelper is a class, then foo and foo.bar are * considered namespaces. */ boolean isNamespace() { return hasClassOrEnumDescendant && type == Type.OBJECTLIT; } /** * Determines whether this is a simple name (as opposed to a qualified * name). */ boolean isSimpleName() { return parent == null; } @Override public String toString() { return fullName() + " (" + type + "): globalSets=" + globalSets + ", localSets=" + localSets + ", totalGets=" + totalGets + ", aliasingGets=" + aliasingGets + ", callGets=" + callGets; } String fullName() { return parent == null ? name : parent.fullName() + '.' + name; } /** * Tries to get the doc info for a given declaration ref. */ private static JSDocInfo getDocInfoForDeclaration(Ref ref) { if (ref.node != null) { Node refParent = ref.node.getParent(); switch (refParent.getType()) { case Token.FUNCTION: case Token.ASSIGN: return refParent.getJSDocInfo(); case Token.VAR: return ref.node == refParent.getFirstChild() ? refParent.getJSDocInfo() : ref.node.getJSDocInfo(); } } return null; } } // ------------------------------------------------------------------------- /** * A global name reference. Contains references to the relevant parse tree * node and its ancestors that may be affected. */ static class Ref { enum Type { SET_FROM_GLOBAL, SET_FROM_LOCAL, PROTOTYPE_GET, ALIASING_GET, // Prevents a name's properties from being collapsed DIRECT_GET, // Prevents a name from being completely eliminated CALL_GET, // Prevents a name from being collapsed if never set } Node node; final Type type; final String sourceName; final Scope scope; final JSModule module; /** * Certain types of references are actually double-refs. For example, * var a = b = 0; * counts as both a "set" of b and an "alias" of b. * * We create two Refs for this node, and mark them as twins of each other. */ private Ref twin = null; /** * Creates a reference at the current node. */ Ref(NodeTraversal t, Node name, Type type) { this.node = name; this.sourceName = t.getSourceName(); this.type = type; this.scope

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> = t.getScope(); this.module = t.getModule(); } private Ref(Ref original, Type type) { this.node = original.node; this.sourceName = original.sourceName; this.type = type; this.scope = original.scope; this.module = original.module; } private Ref(Type type) { this.type = type; this.sourceName = "source"; this.scope = null; this.module = null; } Ref getTwin() { return twin; } boolean isSet() { return type == Type.SET_FROM_GLOBAL || type == Type.SET_FROM_LOCAL; } static void markTwins(Ref a, Ref b) { Preconditions.checkArgument( (a.type == Type.ALIASING_GET || b.type == Type.ALIASING_GET) && (a.type == Type.SET_FROM_GLOBAL || a.type == Type.SET_FROM_LOCAL || b.type == Type.SET_FROM_GLOBAL || b.type == Type.SET_FROM_LOCAL)); a.twin = b; b.twin = a; } /** * Create a new ref that is the same as this one, but of * a different class. */ Ref cloneAndReclassify(Type type) { return new Ref(this, type); } static Ref createRefForTesting(Type type) { return new Ref(type); } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> variable has more than one possible // definition. We set the join of this to be BOTTOM regardless of what // "b" might be. resultMap.put(var, null); continue; } Node aNode = aDef.node; if (b.reachingDef.containsKey(var)) { Definition bDef = b.reachingDef.get(var); if (aDef.equals(bDef)) { resultMap.put(var, aDef); } else { resultMap.put(var, null); } } else { resultMap.put(var, aDef); } } // Take the join of all variables that are not TOP in other but it is TOP // in this. for (Var var : b.reachingDef.keySet()) { if (!a.reachingDef.containsKey(var)) { resultMap.put(var, b.reachingDef.get(var)); } } return result; } } @Override boolean isForward() { return true; } @Override MustDef createEntryLattice() { return new MustDef(jsScope.getVars()); } @Override MustDef createInitialEstimateLattice() { return new MustDef(); } @Override MustDef flowThrough(Node n, MustDef input) { // TODO(user): We are doing a straight copy from input to output. There // might be some opportunities to cut down overhead. MustDef output = new MustDef(input); // TODO(user): This must know about ON_EX edges but it should handle // it better than what we did in liveness. Because we are in a forward mode, // we can used the branched forward analysis. computeMustDef(n, n, output, false); return output; } /** * @param n The node in question. * @param cfgNode The node to add * @param conditional true if the definition is not always executed. */ private void computeMustDef( Node n, Node cfgNode, MustDef output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.WHILE: case Token.DO: case Token.IF: computeMustDef( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); return; case Token.FOR: if (!NodeUtil.isForIn(n)) { computeMustDef( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (NodeUtil.isVar(lhs)) { lhs = lhs.getLastChild(); // for(var x in y) {...} } if (NodeUtil.isName(lhs)) { addToDefIfLocal(lhs.getString(), cfg

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Node, rhs, output); } } return; case Token.AND: case Token.OR: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.HOOK: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.VAR: for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.hasChildren()) { computeMustDef(c.getFirstChild(), cfgNode, output, conditional); addToDefIfLocal(c.getString(), conditional ? null : cfgNode, c.getFirstChild(), output); } } return; default: if (NodeUtil.isAssignmentOp(n) && NodeUtil.isName(n.getFirstChild())) { Node name = n.getFirstChild(); computeMustDef(name.getNext(), cfgNode, output, conditional); addToDefIfLocal(name.getString(), conditional ? null : cfgNode, n.getLastChild(), output); } else { // DEC and INC actually defines the variable. if (n.getType() == Token.DEC || n.getType() == Token.INC) { Node target = n.getFirstChild(); if (NodeUtil.isName(target)) { addToDefIfLocal(target.getString(), conditional ? null : cfgNode, null, output); return; } } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { computeMustDef(c, cfgNode, output, conditional); } } } } /** * Set the variable lattice for the given name to the node value in the def * lattice. Do nothing if the variable name is one of the escaped variable. * * @param node The CFG node where the definition should be record to. * {@code null} if this is a conditional define. */ private void addToDefIfLocal( String name, @Nullable Node node, @Nullable Node rValue, MustDef def) { Var var = jsScope.getVar(name); // var might be null because the variable might be defined in the extern // that we might not traverse. if (var == null || var.scope != jsScope) { return; } for (Var other : def.reachingDef.keySet()) { Definition otherDef = def.reachingDef.get(other); if (otherDef == null) { continue; } if (otherDef.depends.contains(var)) { def.reachingDef.put(other, null); } } if (!escaped.contains(var)) { if (node == null) { def.reachingDef.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>put(var, null); } else { Definition definition = new Definition(node); if (rValue != null) { computeDependence(definition, rValue); } def.reachingDef.put(var, definition); } } } /** * Computes all the local variables that rValue reads from and store that * in the def's depends set. */ private void computeDependence(final Definition def, Node rValue) { NodeTraversal.traverse(compiler, rValue, new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isName(n) && jsScope.isDeclared(n.getString(), true)) { def.depends.add(jsScope.getVar(n.getString())); } } }); } /** * Gets the must reaching definition of a given node. The node must be one of * the control flow graph nodes. * * @param name name of the variable. It can only be names of local variable * that are not function parameters, escaped variables or variables * declared in catch. * @param useNode the location of the use where the definition reaches. */ Node getDef(String name, Node useNode) { Preconditions.checkArgument(getCfg().hasNode(useNode)); GraphNode<Node, Branch> n = getCfg().getNode(useNode); FlowState<MustDef> state = n.getAnnotation(); Definition def = state.getIn().reachingDef.get(jsScope.getVar(name)); if (def == null) { return null; } else { return def.node; } } boolean dependsOnOuterScopeVars(String name, Node useNode) { Preconditions.checkArgument(getCfg().hasNode(useNode)); GraphNode<Node, Branch> n = getCfg().getNode(useNode); FlowState<MustDef> state = n.getAnnotation(); Definition def = state.getIn().reachingDef.get(jsScope.getVar(name)); for (Var s : def.depends) { if (s.scope != jsScope) { return true; } } return false; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.name = file.getName(); this.isExtern = isExtern; } /** Returns a name for this input. Must be unique across all inputs. */ @Override public String getName() { return name; } /** Gets the path relative to closure-base, if one is available. */ @Override public String getPathRelativeToClosureBase() { // TODO(nicksantos): Implement me. throw new UnsupportedOperationException(); } @Override public Node getAstRoot(AbstractCompiler compiler) { return ast.getAstRoot(compiler); } @Override public void clearAst() { ast.clearAst(); } @Override public SourceFile getSourceFile() { return ast.getSourceFile(); } @Override public void setSourceFile(SourceFile file) { ast.setSourceFile(file); } /** Returns the SourceAst object on which this input is based. */ public SourceAst getSourceAst() { return ast; } /** Sets an error manager for routing error messages. */ public void setErrorManager(ErrorManager errorManager) { this.errorManager = errorManager; } /** Sets an abstract compiler for doing parsing. */ public void setCompiler(AbstractCompiler compiler) { this.compiler = compiler; setErrorManager(compiler.getErrorManager()); } /** Gets a list of types depended on by this input. */ @Override public Collection<String> getRequires() { Preconditions.checkNotNull(errorManager, "Expected setErrorManager to be called first"); try { regenerateDependencyInfoIfNecessary(); return Collections.<String>unmodifiableSet(requires); } catch (IOException e) { errorManager.report(CheckLevel.ERROR, JSError.make(AbstractCompiler.READ_ERROR, getName())); return ImmutableList.<String>of(); } } /** Gets a list of types provided by this input. */ @Override public Collection<String> getProvides() { Preconditions.checkNotNull(errorManager, "Expected setErrorManager to be called first"); try { regenerateDependencyInfoIfNecessary(); return Collections.<String>unmodifiableSet(provides); } catch (IOException e) { errorManager.report(CheckLevel.ERROR, JSError.make(AbstractCompiler.READ_ERROR, getName())); return ImmutableList.<String>of(); } } /** * Regenerates the provides/requires if we need to do so. */ private void regenerateDependencyInfoIfNecessary() throws IOException { // If the code is NOT a JsAst, then it was not originally JS code. // Look at the Ast for dependency info. if (!(ast instanceof JsAst)) { Preconditions.checkNotNull(compiler, "Expected setCompiler to be called first"); DepsFinder finder = new DepsFinder(); Node root = getAstRoot(compiler); if (root == null) { return; } finder.visitTree(getAstRoot(compiler)); // TODO(nicksantos|user): This

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> caching behavior is a bit // odd, and only works if you assume the exact call flow that // clients are currently using. In that flow, they call // getProvides(), then remove the goog.provide calls from the // AST, and then call getProvides() again. // // This won't work for any other call flow, or any sort of incremental // compilation scheme. The API needs to be fixed so callers aren't // doing weird things like this, and then we should get rid of the // multiple-scan strategy. provides.addAll(finder.provides); requires.addAll(finder.requires); } else { // Otherwise, look at the source code. if (!generatedDependencyInfoFromSource) { // Note: it's ok to use getName() instead of // getPathRelativeToClosureBase() here because we're not using // this to generate deps files. (We're only using it for // symbol dependencies.) DependencyInfo info = (new JsFileParser(errorManager)).parseFile( getName(), getName(), getCode()); provides.addAll(info.getProvides()); requires.addAll(info.getRequires()); generatedDependencyInfoFromSource = true; } } } private static class DepsFinder { private final List<String> provides = Lists.newArrayList(); private final List<String> requires = Lists.newArrayList(); private final CodingConvention codingConvention = new ClosureCodingConvention(); void visitTree(Node n) { visitSubtree(n, null); } void visitSubtree(Node n, Node parent) { if (n.getType() == Token.CALL) { String require = codingConvention.extractClassNameIfRequire(n, parent); if (require != null) { requires.add(require); } String provide = codingConvention.extractClassNameIfProvide(n, parent); if (provide != null) { provides.add(provide); } return; } else if (parent != null && parent.getType() != Token.EXPR_RESULT && parent.getType() != Token.SCRIPT) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } } } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { return getSourceFile().getLine(lineNumber); } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public Region getRegion(int lineNumber) { return getSourceFile().getRegion(lineNumber); } public String getCode() throws IOException { return getSourceFile().getCode(); } /** Returns the module to which the input belongs. */ public JSModule getModule() { return module; } /** Sets the module to which the input belongs. */ public void setModule(JSModule module) { // An input may only belong to one module. Preconditions.checkArgument( module == null || this.module == null || this.module == module); this.module = module; } public boolean isExtern() { return isExtern; } void setIsExtern(boolean isExtern) { this.isExtern = isExtern; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> unknown * @param charno Column number within line, or -1 for whole line. * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public static JSError make(String sourceName, int lineno, int charno, CheckLevel level, DiagnosticType type, String... arguments) { return new JSError( sourceName, null, lineno, charno, type, level, arguments); } /** * Creates a JSError from a file and Node position. * * @param sourceName The source file name * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public static JSError make(String sourceName, Node n, DiagnosticType type, String... arguments) { return new JSError(sourceName, n, type, arguments); } /** * Creates a JSError from a file and Node position. * * @param sourceName The source file name * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public static JSError make(String sourceName, Node n, CheckLevel level, DiagnosticType type, String... arguments) { return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level, arguments); } // // JSError constructors // /** * Creates a JSError at a CheckLevel for a source file location. * Private to avoid any entanglement with code outside of the compiler. */ private JSError( String sourceName, @Nullable Node node, int lineno, int charno, DiagnosticType type, CheckLevel level, String... arguments) { this.type = type; this.node = node; this.description = type.format.format(arguments); this.lineNumber = lineno; this.charno = charno; this.sourceName = sourceName; this.level = level == null ? type.level : level; } /** * Creates a JSError for a source file location. Private to avoid * any entanglement with code outside of the compiler. */ private JSError(String sourceName, @Nullable Node node, DiagnosticType type, String... arguments) { this(sourceName, node, (node != null) ? node.getLineno() : -1, (node != null) ? node.getCharno() : -1, type, null, arguments); } public DiagnosticType getType() { return type; } /** * Format a message at the given level. * * @return the formatted message or {@code null} */ public String format(CheckLevel level, MessageFormatter formatter) { switch (level) { case ERROR: return formatter.formatError(this); case

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>S_THROW", "The body of a goog.scope function cannot use 'throw'."); static final DiagnosticType GOOG_SCOPE_ALIAS_REDEFINED = DiagnosticType.error( "JSC_GOOG_SCOPE_ALIAS_REDEFINED", "The alias {0} is assigned a value more than once."); static final DiagnosticType GOOG_SCOPE_NON_ALIAS_LOCAL = DiagnosticType.error( "JSC_GOOG_SCOPE_NON_ALIAS_LOCAL", "The local variable {0} is in a goog.scope and is not an alias."); ScopedAliases(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { Traversal traversal = new Traversal(); NodeTraversal.traverse(compiler, root, traversal); if (!traversal.hasErrors()) { // Apply the aliases. for (AliasUsage aliasUsage : traversal.getAliasUsages()) { aliasUsage.applyAlias(); } // Remove the alias definitions. for (Node aliasDefinition : traversal.getAliasDefinitions()) { if (aliasDefinition.getParent().getType() == Token.VAR && aliasDefinition.getParent().hasOneChild()) { aliasDefinition.getParent().detachFromParent(); } else { aliasDefinition.detachFromParent(); } } // Collapse the scopes. for (Node scopeCall : traversal.getScopeCalls()) { Node expressionWithScopeCall = scopeCall.getParent(); Node scopeClosureBlock = scopeCall.getLastChild().getLastChild(); scopeClosureBlock.detachFromParent(); expressionWithScopeCall.getParent().replaceChild( expressionWithScopeCall, scopeClosureBlock); NodeUtil.tryMergeBlock(scopeClosureBlock); } if (traversal.getAliasUsages().size() > 0 || traversal.getAliasDefinitions().size() > 0 || traversal.getScopeCalls().size() > 0) { compiler.reportCodeChange(); } } } private interface AliasUsage { public void applyAlias(); } private class AliasedNode implements AliasUsage { private final Node aliasReference; private final Node aliasDefinition; AliasedNode(Node aliasReference, Node aliasDefinition) { this.aliasReference = aliasReference; this.aliasDefinition = aliasDefinition; } public void applyAlias() { aliasReference.getParent().replaceChild( aliasReference, aliasDefinition.cloneTree()); } } private class AliasedTypeNode implements AliasUsage { private final Node aliasReference; private final String correctedType; AliasedTypeNode(Node aliasReference, String correctedType) { this.aliasReference = aliasReference; this.correctedType = correctedType; } public void applyAlias() { aliasReference.setString(correctedType); } } private class Traversal implements NodeTraversal.ScopedCallback { // The job of this class is to collect these three data sets. private List<Node> aliasDefinitions = Lists.newArrayList(); private List<

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Node> scopeCalls = Lists.newArrayList(); private List<AliasUsage> aliasUsages = Lists.newArrayList(); // This map is temporary and cleared for each scope. private Map<String, Var> aliases = Maps.newHashMap(); private boolean hasErrors = false; List<Node> getAliasDefinitions() { return aliasDefinitions; } private List<AliasUsage> getAliasUsages() { return aliasUsages; } List<Node> getScopeCalls() { return scopeCalls; } boolean hasErrors() { return hasErrors; } private boolean isCallToScopeMethod(Node n) { return n.getType() == Token.CALL && SCOPING_METHOD_NAME.equals(n.getFirstChild().getQualifiedName()); } @Override public void enterScope(NodeTraversal t) { } @Override public void exitScope(NodeTraversal t) { if (t.getScopeDepth() == 2) { aliases.clear(); } } @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.FUNCTION && t.inGlobalScope()) { // Do not traverse in to functions except for goog.scope functions. if (parent == null || !isCallToScopeMethod(parent)) { return false; } } return true; } private void report(NodeTraversal t, Node n, DiagnosticType error, String... arguments) { compiler.report(t.makeError(n, error, arguments)); hasErrors = true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (isCallToScopeMethod(n)) { if (!NodeUtil.isExpressionNode(parent)) { report(t, n, GOOG_SCOPE_USED_IMPROPERLY); } if (n.getChildCount() != 2) { // The goog.scope call should have exactly 1 parameter. The first // child is the "goog.scope" and the second should be the parameter. report(t, n, GOOG_SCOPE_HAS_BAD_PARAMETERS); } else { Node anonymousFnNode = n.getChildAtIndex(1); if (!NodeUtil.isFunction(anonymousFnNode) || NodeUtil.getFunctionName(anonymousFnNode) != null || NodeUtil.getFnParameters(anonymousFnNode).hasChildren()) { report(t, anonymousFnNode, GOOG_SCOPE_HAS_BAD_PARAMETERS); } else { scopeCalls.add(n); } } } if (t.getScopeDepth() == 2) { int type = n.getType(); if (type == Token.NAME && parent.getType() == Token.VAR) { if (n.hasChildren() && n.getFirstChild().isQualifiedName()) { aliases.put(n.getString(), t.getScope().getVar(n.getString())); aliasDefinitions.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>add(n); // If we found an alias, we are done. return; } else { // TODO(robbyw): Support using locals for private variables. report(t, n, GOOG_SCOPE_NON_ALIAS_LOCAL, n.getString()); } } if (type == Token.NAME && NodeUtil.isAssignmentOp(parent) && n == parent.getFirstChild()) { report(t, n, GOOG_SCOPE_ALIAS_REDEFINED, n.getString()); } if (type == Token.RETURN) { report(t, n, GOOG_SCOPE_USES_RETURN); } else if (type == Token.THIS) { report(t, n, GOOG_SCOPE_REFERENCES_THIS); } else if (type == Token.THROW) { report(t, n, GOOG_SCOPE_USES_THROW); } } if (t.getScopeDepth() >= 2) { if (n.getType() == Token.NAME) { String name = n.getString(); Var aliasVar = aliases.get(name); // Check if this name points to an alias. if (aliasVar != null && t.getScope().getVar(name) == aliasVar) { // Note, to support the transitive case, it's important we don't // clone aliasedNode here. For example, // var g = goog; var d = g.dom; d.createElement('DIV'); // The node in aliasedNode (which is "g") will be replaced in the // changes pass above with "goog". If we cloned here, we'd end up // with <code>g.dom.createElement('DIV')</code>. Node aliasedNode = aliasVar.getInitialValue(); aliasUsages.add(new AliasedNode(n, aliasedNode)); } } JSDocInfo info = n.getJSDocInfo(); if (info != null) { for (Node node : info.getTypeNodes()) { fixTypeNode(node); } } // TODO(robbyw): Error for goog.scope not at root. } } private void fixTypeNode(Node typeNode) { if (typeNode.getType() == Token.STRING) { String name = typeNode.getString(); int endIndex = name.indexOf('.'); if (endIndex == -1) { endIndex = name.length(); } String baseName = name.substring(0, endIndex); Var aliasVar = aliases.get(baseName); if (aliasVar != null) { Node aliasedNode = aliasVar.getInitialValue(); aliasUsages.add(new AliasedTypeNode(typeNode, aliasedNode.getQualifiedName() + name.substring(endIndex))); } } for (Node child = typeNode.getFirstChild(); child != null; child = child.getNext()) { fixTypeNode(child);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { /** Warnings guards for each suppressable warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressable groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressableGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressableGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; // We only care about function annotations at the FUNCTION and SCRIPT // level. Otherwise, the @suppress annotation has an implicit // dependency on the exact structure of our AST, and that seems like // a bad idea. if (type == Token.FUNCTION) { info = NodeUtil.getFunctionInfo(current); } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor); // Some @suppress tags are for other tools, and // may not have a warnings guard. if (guard != null) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { return newLevel; } } } } } } return null;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> functionNode); abstract T processIfStatement(IfStatement statementNode); abstract T processInfixExpression(InfixExpression exprNode); abstract T processKeywordLiteral(KeywordLiteral literalNode); abstract T processLabel(Label labelNode); abstract T processLabeledStatement(LabeledStatement statementNode); abstract T processName(Name nameNode); abstract T processNewExpression(NewExpression exprNode); abstract T processNumberLiteral(NumberLiteral literalNode); abstract T processObjectLiteral(ObjectLiteral literalNode); abstract T processObjectProperty(ObjectProperty propertyNode); abstract T processParenthesizedExpression(ParenthesizedExpression exprNode); abstract T processPropertyGet(PropertyGet getNode); abstract T processRegExpLiteral(RegExpLiteral literalNode); abstract T processReturnStatement(ReturnStatement statementNode); abstract T processScope(Scope scopeNode); abstract T processStringLiteral(StringLiteral literalNode); abstract T processSwitchCase(SwitchCase caseNode); abstract T processSwitchStatement(SwitchStatement statementNode); abstract T processThrowStatement(ThrowStatement statementNode); abstract T processTryStatement(TryStatement statementNode); abstract T processUnaryExpression(UnaryExpression exprNode); abstract T processVariableDeclaration(VariableDeclaration declarationNode); abstract T processVariableInitializer(VariableInitializer initializerNode); abstract T processWhileLoop(WhileLoop loopNode); abstract T processWithStatement(WithStatement statementNode); abstract T processIllegalToken(AstNode node); public T process(AstNode node) { switch (node.getType()) { case Token.ADD: case Token.AND: case Token.BITAND: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.IN: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.OR: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return processInfixExpression((InfixExpression) node); case Token.ARRAYLIT: return processArrayLiteral((ArrayLiteral) node); case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_DIV: case Token.ASSIGN_LSH: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_RSH: case Token.ASSIGN_SUB: case Token.ASSIGN_URSH: return processAssignment((Assignment) node); case Token.BITNOT: case Token.DEC: case Token.DELPROP: case Token.INC: case Token.NEG: case Token.NOT: case Token.POS: case Token.TYPEOF:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case Token.VOID: return processUnaryExpression((UnaryExpression) node); case Token.BLOCK: if (node instanceof Block) { return processBlock((Block) node); } else if (node instanceof Scope) { return processScope((Scope) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.BREAK: return processBreakStatement((BreakStatement) node); case Token.CALL: return processFunctionCall((FunctionCall) node); case Token.CASE: case Token.DEFAULT: return processSwitchCase((SwitchCase) node); case Token.CATCH: case Token.FINALLY: return processCatchClause((CatchClause) node); case Token.COLON: return processObjectProperty((ObjectProperty) node); case Token.CONTINUE: return processContinueStatement((ContinueStatement) node); case Token.DO: return processDoLoop((DoLoop) node); case Token.EMPTY: return processEmptyExpression((EmptyExpression) node); case Token.EXPR_RESULT: case Token.EXPR_VOID: if (node instanceof ExpressionStatement) { return processExpressionStatement((ExpressionStatement) node); } else if (node instanceof LabeledStatement) { return processLabeledStatement((LabeledStatement) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.DEBUGGER: case Token.FALSE: case Token.NULL: case Token.THIS: case Token.TRUE: return processKeywordLiteral((KeywordLiteral) node); case Token.FOR: if (node instanceof ForInLoop) { return processForInLoop((ForInLoop) node); } else if (node instanceof ForLoop) { return processForLoop((ForLoop) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.FUNCTION: return processFunctionNode((FunctionNode) node); case Token.GETELEM: return processElementGet((ElementGet) node); case Token.GETPROP: return processPropertyGet((PropertyGet) node); case Token.HOOK: return processConditionalExpression((ConditionalExpression) node); case Token.IF: return processIfStatement((IfStatement) node); case Token.LABEL: return processLabel((Label) node); case Token.LP: return processParenthesizedExpression((ParenthesizedExpression) node); case Token.NAME: return processName((Name) node); case Token.NEW: return processNewExpression((NewExpression) node); case Token.NUMBER: return processNumberLiteral((NumberLiteral) node); case Token.OBJECTLIT:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return processObjectLiteral((ObjectLiteral) node); case Token.REGEXP: return processRegExpLiteral((RegExpLiteral) node); case Token.RETURN: return processReturnStatement((ReturnStatement) node); case Token.SCRIPT: return processAstRoot((AstRoot) node); case Token.STRING: return processStringLiteral((StringLiteral) node); case Token.SWITCH: return processSwitchStatement((SwitchStatement) node); case Token.THROW: return processThrowStatement((ThrowStatement) node); case Token.TRY: return processTryStatement((TryStatement) node); case Token.VAR: if (node instanceof VariableDeclaration) { return processVariableDeclaration((VariableDeclaration) node); } else if (node instanceof VariableInitializer) { return processVariableInitializer((VariableInitializer) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.WHILE: return processWhileLoop((WhileLoop) node); case Token.WITH: return processWithStatement((WithStatement) node); } return processIllegalToken(node); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * NodeUtil contains utilities that get properties from the Node object. * */ public final class NodeUtil { final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty"; // TODO(user): Eliminate this class and make all of the static methods // instance methods of com.google.javascript.rhino.Node. /** the set of builtin constructors that don't have side effects. */ private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS = new HashSet<String>(Arrays.asList( "Array", "Date", "Error", "Object", "RegExp", "XMLHttpRequest")); // Utility class; do not instantiate. private NodeUtil() {} /** * Gets the boolean value of a node that represents a expression. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. * Note: unlike getBooleanValue this function does not return UNKNOWN * for expressions with side-effects. */ static TernaryValue getExpressionBooleanValue(Node n) { switch (n.getType()) { case Token.ASSIGN: case Token.COMMA: // For ASSIGN and COMMA the value is the value of the RHS. return getExpressionBooleanValue(n.getLastChild()); case Token.NOT: TernaryValue value = getExpressionBooleanValue(n.getLastChild

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>()); return value.not(); case Token.AND: { TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild()); TernaryValue rhs = getExpressionBooleanValue(n.getLastChild()); return lhs.and(rhs); } case Token.OR: { TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild()); TernaryValue rhs = getExpressionBooleanValue(n.getLastChild()); return lhs.or(rhs); } case Token.HOOK: { TernaryValue trueValue = getExpressionBooleanValue( n.getFirstChild().getNext()); TernaryValue falseValue = getExpressionBooleanValue(n.getLastChild()); if (trueValue.equals(falseValue)) { return trueValue; } else { return TernaryValue.UNKNOWN; } } default: return getBooleanValue(n); } } /** * Gets the boolean value of a node that represents a literal. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. */ static TernaryValue getBooleanValue(Node n) { switch (n.getType()) { case Token.STRING: return TernaryValue.forBoolean(n.getString().length() > 0); case Token.NUMBER: return TernaryValue.forBoolean(n.getDouble() != 0); case Token.NULL: case Token.FALSE: case Token.VOID: return TernaryValue.FALSE; case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "NaN".equals(name)) { // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return TernaryValue.FALSE; } else if ("Infinity".equals(name)) { return TernaryValue.TRUE; } break; case Token.TRUE: case Token.ARRAYLIT: case Token.OBJECTLIT: case Token.REGEXP: return TernaryValue.TRUE; } return TernaryValue.UNKNOWN; } /** * Gets the value of a node as a String, or null if it cannot be converted. * When it returns a non-null String, this method effectively emulates the * <code>String()</code> JavaScript cast function. */ static String getStringValue(Node n) { // TODO(user): Convert constant array, object, and regex literals as well. switch (n.getType()) { case Token.STRING: return n.getString(); case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name)) { return name; } break; case Token.NUMBER: double value = n.getDouble(); long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return Long.toString(longValue); } else { return Double.toString(n.getDouble()); } case Token.FALSE: case Token.TRUE: case Token.NULL: return Node.tokenToName(n.getType()); case Token.VOID: return "undefined"; } return null; } /** * Gets the value of a node as a Number, or null if it cannot be converted. * When it returns a non-null Double, this method effectively emulates the * <code>Number()</code> JavaScript cast function. */ static Double getNumberValue(Node n) { switch (n.getType()) { case Token.TRUE: return 1.0; case Token.FALSE: case Token.NULL: return 0.0; case Token.NUMBER: return n.getDouble(); case Token.VOID: return Double.NaN; case Token.NAME: String name = n.getString(); if (name.equals("undefined")) { return Double.NaN; } if (name.equals("NaN")) { return Double.NaN; } if (name.equals("Infinity")) { return Double.POSITIVE_INFINITY; } return null; } return null; } /** * Gets the function's name. This method recognizes five forms: * <ul> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * In two last cases with named function expressions, the second name is * returned (the variable of qualified name). * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getFunctionName(Node n) { Node parent = n.getParent(); String name = n.getFirstChild().getString(); switch (parent.getType()) { case Token.NAME: // var name = function() ... // var name2 = function name1() ... return parent.getString(); case Token.ASSIGN: // qualified.name = function() ... // qualified.name2 = function name1() ... return parent.getFirstChild().getQualifiedName(); default: // function name() ... return name != null && name.length() != 0 ? name : null; } } /** * Gets the function's name. This method recognizes the forms: * <ul> * <li>{@code &#123;'name': function() ...&#125;}</li> * <li>{@code &#123;name: function() ...&#125;}</li> * <li>{@code function name

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getNearestFunctionName(Node n) { String name = getFunctionName(n); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.STRING: // Return the name of the literal's key. return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if this is a literal value. We define a literal value * as any node that evaluates to the same thing regardless of when or * where it is evaluated. So /xyz/ and [3, 5] are literals, but * the name a is not. * * Function literals do not meet this definition, because they * lexically capture variables. For example, if you have * <code> * function() { return a; } * </code> * If it is evaluated in a different scope, then it * captures a different variable. Even if the function did not read * any captured vairables directly, it would still fail this definition, * because it affects the lifecycle of variables in the enclosing scope. * * However, a function literal with respect to a particular scope is * a literal. * * @param includeFunctions If true, all function expressions will be * treated as literals. */ static boolean isLiteralValue(Node n, boolean includeFunctions) { switch (n.getType()) { case Token.ARRAYLIT: case Token.REGEXP: // Return true only if all children are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!is

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>LiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) { return false; } } return true; case Token.FUNCTION: return includeFunctions && !NodeUtil.isFunctionDeclaration(n); default: return isImmutableValue(n); } } /** * Determines whether the given value may be assigned to a define. * * @param val The value being assigned. * @param defines The list of names of existing defines. */ static boolean isValidDefineValue(Node val, Set<String> defines) { switch (val.getType()) { case Token.STRING: case Token.NUMBER: case Token.TRUE: case Token.FALSE: return true; // Binary operators are only valid if both children are valid. case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return isValidDefineValue(val.getFirstChild(), defines) && isValidDefineValue(val.getLastChild(), defines); // Uniary operators are valid if the child is valid. case Token.NOT: case Token.NEG: case Token.POS: return isValidDefineValue(val.getFirstChild(), defines); // Names are valid if and only if they are defines themselves. case Token.NAME: case Token.GETPROP: if (val.isQualifiedName()) { return defines.contains(val.getQualifiedName()); } } return false; } /** * Returns whether this a BLOCK node with no children. * * @param block The node. */ static boolean isEmptyBlock(Node block) { if (block.getType() != Token.BLOCK) { return false; } for (Node n = block.getFirstChild(); n != null; n = n.getNext()) { if (n.getType() != Token.EMPTY) { return false; } } return true; } static boolean isSimpleOperator(Node n) { return isSimpleOperatorType(n.getType()); } /** * A "simple" operator is one whose children are expressions, * has no direct side-effects (unlike '+='), and has no * conditional aspects (unlike '||'). */ static boolean isSimpleOperatorType

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(int type) { switch (type) { case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GETELEM: case Token.GETPROP: case Token.GT: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.NOT: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.TYPEOF: case Token.VOID: case Token.POS: case Token.NEG: case Token.URSH: return true; default: return false; } } /** * Creates an EXPR_RESULT. * * @param child The expression itself. * @return Newly created EXPR node with the child as subexpression. */ public static Node newExpr(Node child) { Node expr = new Node(Token.EXPR_RESULT, child) .copyInformationFrom(child); return expr; } /** * Returns true if the node may create new mutable state, or change existing * state. * * @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a> */ static boolean mayEffectMutableState(Node n) { return mayEffectMutableState(n, null); } static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, true, compiler); } /** * Returns true if the node which may have side effects when executed. */ static boolean mayHaveSideEffects(Node n) { return mayHaveSideEffects(n, null); } static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, false, compiler); } /** * Returns true if some node in n's subtree changes application state. * If {@code checkForNewObjects} is true, we assume that newly created * mutable objects (like object literals) change state. Otherwise, we assume * that they have no side effects. */ private static boolean checkForStateChangeHelper( Node n, boolean checkForNewObjects, AbstractCompiler compiler) { // Rather than id which ops may have side effects, id the ones // that we know to be safe switch (n.getType()) { // other side-effect free statements and expressions case Token.AND: case Token.BLOCK: case Token.EXPR_RESULT: case Token.HOOK: case Token.IF: case Token.IN: case Token.LP: case Token.NUMBER: case Token.OR: case Token.THIS

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: case Token.SWITCH: case Token.TRY: case Token.EMPTY: break; // Throws are by definition side effects case Token.THROW: return true; case Token.OBJECTLIT: if (checkForNewObjects) { return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper( c.getFirstChild(), checkForNewObjects, compiler)) { return true; } } return false; case Token.ARRAYLIT: case Token.REGEXP: if (checkForNewObjects) { return true; } break; case Token.VAR: // empty var statement (no declaration) case Token.NAME: // variable by itself if (n.getFirstChild() != null) { return true; } break; case Token.FUNCTION: // Function expressions don't have side-effects, but function // declarations change the namespace. Either way, we don't need to // check the children, since they aren't executed at declaration time. return checkForNewObjects || !isFunctionExpression(n); case Token.NEW: if (checkForNewObjects) { return true; } if (!constructorCallHasSideEffects(n)) { // loop below will see if the constructor parameters have // side-effects break; } return true; case Token.CALL: // calls to functions that have no side effects have the no // side effect property set. if (!functionCallHasSideEffects(n, compiler)) { // loop below will see if the function parameters have // side-effects break; } return true; default: if (isSimpleOperatorType(n.getType())) { break; } if (isAssignmentOp(n)) { Node assignTarget = n.getFirstChild(); if (isName(assignTarget)) { return true; } // Assignments will have side effects if // a) The RHS has side effects, or // b) The LHS has side effects, or // c) A name on the LHS will exist beyond the life of this statement. if (checkForStateChangeHelper( n.getFirstChild(), checkForNewObjects, compiler) || checkForStateChangeHelper( n.getLastChild(), checkForNewObjects, compiler)) { return true; } if (isGet(assignTarget)) { // If the object being assigned to is a local object, don't // consider this a side-effect as it can't be referenced // elsewhere. Don't do this recursively as the property might // be an alias of another object, unlike a literal below. Node current = assignTarget.getFirstChild(); if (evaluatesToLocalValue(current)) { return false; } // A literal value as defined by "isLiteralValue

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>" is guaranteed // not to be an alias, or any components which are aliases of // other objects. // If the root object is a literal don't consider this a // side-effect. while (isGet(current)) { current = current.getFirstChild(); } return !isLiteralValue(current, true); } else { // TODO(johnlenz): remove this code and make this an exception. This // is here only for legacy reasons, the AST is not valid but // preserve existing behavior. return !isLiteralValue(assignTarget, true); } } return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) { return true; } } return false; } /** * Do calls to this constructor have side effects? * * @param callNode - construtor call node */ static boolean constructorCallHasSideEffects(Node callNode) { return constructorCallHasSideEffects(callNode, null); } static boolean constructorCallHasSideEffects( Node callNode, AbstractCompiler compiler) { if (callNode.getType() != Token.NEW) { throw new IllegalStateException( "Expected NEW node, got " + Token.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } Node nameNode = callNode.getFirstChild(); if (nameNode.getType() == Token.NAME && CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) { return false; } return true; } // A list of built-in object creation or primitive type cast functions that // can also be called as constructors but lack side-effects. // TODO(johnlenz): consider adding an extern annotation for this. private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS = ImmutableSet.of( "Object", "Array", "String", "Number", "Boolean", "RegExp", "Error"); private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS = ImmutableSet.of("toString", "valueOf"); private static final Set<String> REGEXP_METHODS = ImmutableSet.of("test", "exec"); private static final Set<String> STRING_REGEXP_METHODS = ImmutableSet.of("match", "replace", "search", "split"); /** * Returns true if calls to this function have side effects. * * @param callNode - function call node */ static boolean functionCallHasSideEffects( Node callNode) { return functionCallHasSideEffects(callNode, null); } /** * Returns true if calls to this function have side effects. * * @param callNode The call node to inspected. * @param compiler A compiler object to provide program

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> state changing * context information. Can be null. */ static boolean functionCallHasSideEffects( Node callNode, @Nullable AbstractCompiler compiler) { if (callNode.getType() != Token.CALL) { throw new IllegalStateException( "Expected CALL node, got " + Token.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } Node nameNode = callNode.getFirstChild(); // Built-in functions with no side effects. if (nameNode.getType() == Token.NAME) { String name = nameNode.getString(); if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) { return false; } } else if (nameNode.getType() == Token.GETPROP) { if (callNode.hasOneChild() && OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains( nameNode.getLastChild().getString())) { return false; } if (callNode.isOnlyModifiesThisCall() && evaluatesToLocalValue(nameNode.getFirstChild())) { return false; } // Functions in the "Math" namespace have no side effects. if (nameNode.getFirstChild().getType() == Token.NAME) { String namespaceName = nameNode.getFirstChild().getString(); if (namespaceName.equals("Math")) { return false; } } if (compiler != null && !compiler.hasRegExpGlobalReferences()) { if (nameNode.getFirstChild().getType() == Token.REGEXP && REGEXP_METHODS.contains(nameNode.getLastChild().getString())) { return false; } else if (nameNode.getFirstChild().getType() == Token.STRING && STRING_REGEXP_METHODS.contains( nameNode.getLastChild().getString())) { Node param = nameNode.getNext(); if (param != null && (param.getType() == Token.STRING || param.getType() == Token.REGEXP)) return false; } } } return true; } /** * @return Whether the call has a local result. */ static boolean callHasLocalResult(Node n) { Preconditions.checkState(n.getType() == Token.CALL); return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0; } /** * Returns true if the current node's type implies side effects. * * This is a non-recursive version of the may have side effects * check; used to check wherever the current node's type is one of * the reason's why a subtree has side effects. */ static boolean nodeTypeMayHaveSideEffects(Node n) { return nodeTypeMayHaveSideEffects(n, null); } static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) { if (isAssignmentOp(n)) { return true; } switch(n.getType

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>()) { case Token.DELPROP: case Token.DEC: case Token.INC: case Token.THROW: return true; case Token.CALL: return NodeUtil.functionCallHasSideEffects(n, compiler); case Token.NEW: return NodeUtil.constructorCallHasSideEffects(n, compiler); case Token.NAME: // A variable definition. return n.hasChildren(); default: return false; } } /** * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n) { Set<String> emptySet = Collections.emptySet(); return canBeSideEffected(n, emptySet); } /** * @param knownConstants A set of names known to be constant value at * node 'n' (such as locals that are last written before n can execute). * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; case Token.FUNCTION: // Function expression are not changed by side-effects, // and function declarations are not part of expressions. Preconditions.checkState(isFunctionExpression(n)); return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: * 3 logical-or || * 4 logical-and && * 5 bitwise-or | * 6 bitwise-xor ^ * 7 bitwise-and & * 8 equality == != * 9 relational < <= > >= * 10 bitwise shift << >> >>> * 11 addition/subtraction + - * 12 multiply/divide * / % * 13 negation/increment ! ~ - ++ -- * 14 call, member () [] . */ static int precedence(int type) { switch (type) { case Token.COMMA: return 0; case Token.ASSIGN_BITOR: case

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN: return 1; case Token.HOOK: return 2; // ?: operator case Token.OR: return 3; case Token.AND: return 4; case Token.BITOR: return 5; case Token.BITXOR: return 6; case Token.BITAND: return 7; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: return 8; case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.INSTANCEOF: case Token.IN: return 9; case Token.LSH: case Token.RSH: case Token.URSH: return 10; case Token.SUB: case Token.ADD: return 11; case Token.MUL: case Token.MOD: case Token.DIV: return 12; case Token.INC: case Token.DEC: case Token.NEW: case Token.DELPROP: case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: return 13; case Token.ARRAYLIT: case Token.CALL: case Token.EMPTY: case Token.FALSE: case Token.FUNCTION: case Token.GETELEM: case Token.GETPROP: case Token.GET_REF: case Token.IF: case Token.LP: case Token.NAME: case Token.NULL: case Token.NUMBER: case Token.OBJECTLIT: case Token.REGEXP: case Token.RETURN: case Token.STRING: case Token.THIS: case Token.TRUE: return 15; default: throw new Error("Unknown precedence for " + Node.tokenToName(type) + " (type " + type + ")"); } } /** * Returns true if the operator is associative. * e.g. (a * b) * c = a * (b * c) * Note: "+" is not associative because it is also the concatenation * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 */ static boolean isAssociative(int type) { switch (type) { case Token.MUL: case Token.AND: case Token.OR: case Token.BITOR: case Token.BITAND:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return true; default: return false; } } /** * Returns true if the operator is commutative. * e.g. (a * b) * c = c * (b * a) * Note 1: "+" is not commutative because it is also the concatenation * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 * Note 2: only operations on literals and pure functions are commutative. */ static boolean isCommutative(int type) { switch (type) { case Token.MUL: case Token.BITOR: case Token.BITAND: return true; default: return false; } } static boolean isAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: return true; } return false; } static int getOpFromAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN_BITOR: return Token.BITOR; case Token.ASSIGN_BITXOR: return Token.BITXOR; case Token.ASSIGN_BITAND: return Token.BITAND; case Token.ASSIGN_LSH: return Token.LSH; case Token.ASSIGN_RSH: return Token.RSH; case Token.ASSIGN_URSH: return Token.URSH; case Token.ASSIGN_ADD: return Token.ADD; case Token.ASSIGN_SUB: return Token.SUB; case Token.ASSIGN_MUL: return Token.MUL; case Token.ASSIGN_DIV: return Token.DIV; case Token.ASSIGN_MOD: return Token.MOD; } throw new IllegalArgumentException("Not an assiment op"); } static boolean isExpressionNode(Node n) { return n.getType() == Token.EXPR_RESULT; } /** * Determines if the given node contains a function statement or function * expression. */ static boolean containsFunction(Node n) { return containsType(n, Token.FUNCTION); } /** * Returns true if the shallow scope contains references to 'this' keyword */ static boolean referencesThis(Node n) { return containsType(n, Token.THIS, new MatchNotFunction()); } /** * Is this a GETPROP or GETELEM node? */ static boolean isGet(Node n) { return n.getType() == Token.GETPROP || n.getType() == Token.GETELEM

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; } /** * Is this a GETPROP node? */ static boolean isGetProp(Node n) { return n.getType() == Token.GETPROP; } /** * Is this a NAME node? */ static boolean isName(Node n) { return n.getType() == Token.NAME; } /** * Is this a NEW node? */ static boolean isNew(Node n) { return n.getType() == Token.NEW; } /** * Is this a VAR node? */ static boolean isVar(Node n) { return n.getType() == Token.VAR; } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR; } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(isName(n)); Node parent = n.getParent(); if (isVar(parent)) { return n.getFirstChild(); } else if (isAssign(parent) && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this a STRING node? */ static boolean isString(Node n) { return n.getType() == Token.STRING; } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN; } /** * Is this an ASSIGN node? */ static boolean isAssign(Node n) { return n.getType() == Token.ASSIGN; } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.CALL; } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.getType() == Token.FOR && n.getChildCount() == 3;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (NodeUtil.isFunction(parent)) { break; } } return false; } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WHILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> a node */ static Node getConditionExpression(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n); case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK; } /** * @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { Node parent = n.getParent(); // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node can be // either part of an expression or a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.getType() == Token.CASE || n.getType() == Token.DEFAULT; } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return isName(n) && !n.getString().isEmpty(); } /** @return Whether the node is a label name. */ static boolean isLabelName(Node n) { return (n != null && n.getType() == Token.LABEL_NAME); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.getType() == Token.TRY && parent.getChildCount() == 3 && child == parent.getLastChild(); } /** Safely remove children while maintaining a valid node structure. */ static void removeChild(Node parent, Node node) { // Node parent = node.getParent(); if (isStatementBlock(parent) || isSwitchCase(node) || isTryFinallyNode(parent, node)) { // A statement in a block can simply be removed. parent.removeChild(node); } else if (parent.getType() == Token.VAR) { if (parent.hasMoreThanOneChild()) { parent.removeChild(node); } else { // Remove the node from the parent,

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> so it can be reused. parent.removeChild(node); // This would leave an empty VAR, remove the VAR itself. removeChild(parent.getParent(), parent); } } else if (node.getType() == Token.BLOCK) { // Simply empty the block. This maintains source location and // "synthetic"-ness. node.detachChildren(); } else if (parent.getType() == Token.LABEL && node == parent.getLastChild()) { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // A LABEL without children can not be referred to, remove it. removeChild(parent.getParent(), parent); } else if (parent.getType() == Token.FOR && parent.getChildCount() == 4) { // Only Token.FOR can have an Token.EMPTY other control structure // need something for the condition. Others need to be replaced // or the structure removed. parent.replaceChild(node, new Node(Token.EMPTY)); } else { throw new IllegalStateException("Invalid attempt to remove node: " + node.toString() + " of "+ parent.toString()); } } /** * Merge a block with its parent block. * @return Whether the block was removed. */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.getType() == Token.BLOCK); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else { return false; } } /** * Is this a CALL node? */ static boolean isCall(Node n) { return n.getType() == Token.CALL; } /** * @param node A node * @return Whether the call is a NEW or CALL node. */ static boolean isCallOrNew(Node node) { return NodeUtil.isCall(node) || NodeUtil.isNew(node); } /** * Is this a FUNCTION node? */ static boolean isFunction(Node n) { return n.getType() == Token.FUNCTION; } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(isFunction(fn)); return fn.getLastChild(); } /** * Is this a THIS node? */ static boolean isThis(Node node) { return node.getType() == Token.THIS; } /** * Is this node or any of its children a CALL? */ static boolean containsCall(Node n) { return containsType(n, Token.CALL

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not part of a expression; see {@link #isFunctionExpression}). */ static boolean isFunctionDeclaration(Node n) { return n.getType() == Token.FUNCTION && isStatement(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration}). */ static boolean isHoistedFunctionDeclaration(Node n) { return isFunctionDeclaration(n) && (n.getParent().getType() == Token.SCRIPT || n.getParent().getParent().getType() == Token.FUNCTION); } /** * Is a FUNCTION node an function expression? An function expression is one * that has either no name or a name that is not added to the current scope. * * <p>Some examples of function expressions: * <pre> * (function () {}) * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;) {} * </pre> * * <p>Some examples of functions that are <em>not</em> expressions: * <pre> * function f() {} * if (x); else function f() {} * for (;;) { function f() {} } * </pre> * * @param n A node * @return Whether n is an function used within an expression. */ static boolean isFunctionExpression(Node n) { return n.getType() == Token.FUNCTION && !isStatement(n); } /** * Determines if a node is a function expression that has an empty body. * * @param node a node * @return whether the given node is a function expression that is empty */ static boolean isEmptyFunctionExpression(Node node) { return isFunctionExpression(node) && isEmptyBlock(node.getLastChild()); } /** * Determines if a function takes a variable number of arguments by * looking for references to the "arguments" var_args object. */ static boolean isVarArgsFunction(Node function) { Preconditions.checkArgument(isFunction(function)); return isNameReferenced( function.getLastChild(), "arguments", new MatchNotFunction()); } /** * @return Whether node is a call to methodName. * a.f(...) * a['f'](...) */ static boolean isObjectCallMethod(Node callNode, String methodName) { if (callNode.getType() == Token.CALL) { Node functionIndentifyingExpression = callNode.getFirstChild(); if (isGet(functionIndentifyingExpression)) { Node last = functionIndentifyingExpression.getLastChild(); if (last != null && last.getType() == Token.STRING) { String propName = last

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.getString(); return (propName.equals(methodName)); } } } return false; } /** * @return Whether the callNode represents an expression in the form of: * x.call(...) * x['call'](...) */ static boolean isFunctionObjectCall(Node callNode) { return isObjectCallMethod(callNode, "call"); } /** * @return Whether the callNode represents an expression in the form of: * x.apply(...) * x['apply'](...) */ static boolean isFunctionObjectApply(Node callNode) { return isObjectCallMethod(callNode, "apply"); } /** * @return Whether the callNode represents an expression in the form of: * x.apply(...) * x['apply'](...) * or * x.call(...) * x['call'](...) */ static boolean isFunctionObjectCallOrApply(Node callNode) { return isFunctionObjectCall(callNode) || isFunctionObjectApply(callNode); } /** * @return Whether the callNode represents an expression in the form of: * x.call(...) * x['call'](...) * where x is a NAME node. */ static boolean isSimpleFunctionObjectCall(Node callNode) { if (isFunctionObjectCall(callNode)) { if (callNode.getFirstChild().getFirstChild().getType() == Token.NAME) { return true; } } return false; } /** * Determines whether this node is strictly on the left hand side of an assign * or var initialization. Notably, this does not include all L-values, only * statements where the node is used only as an L-value. * * @param n The node * @param parent Parent of the node * @return True if n is the left hand of an assign */ static boolean isLhs(Node n, Node parent) { return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) || parent.getType() == Token.VAR; } /** * Determines whether a node represents an object literal key * (e.g. key1 in {key1: value1, key2: value2}). * * @param node A node * @param parent The node's parent */ static boolean isObjectLitKey(Node node, Node parent) { switch (node.getType()) { case Token.NUMBER: case Token.STRING: return parent.getType() == Token.OBJECTLIT; case Token.GET: case Token.SET: return true; } return false; } /** * Converts an operator's token value (see {@link Token}) to a string * representation. * * @param operator the operator's token value to convert * @return the string representation or {@code null} if the token value is * not an operator */ static String op

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ToStr(int operator) { switch (operator) { case Token.BITOR: return "|"; case Token.OR: return "||"; case Token.BITXOR: return "^"; case Token.AND: return "&&"; case Token.BITAND: return "&"; case Token.SHEQ: return "==="; case Token.EQ: return "=="; case Token.NOT: return "!"; case Token.NE: return "!="; case Token.SHNE: return "!=="; case Token.LSH: return "<<"; case Token.IN: return "in"; case Token.LE: return "<="; case Token.LT: return "<"; case Token.URSH: return ">>>"; case Token.RSH: return ">>"; case Token.GE: return ">="; case Token.GT: return ">"; case Token.MUL: return "*"; case Token.DIV: return "/"; case Token.MOD: return "%"; case Token.BITNOT: return "~"; case Token.ADD: return "+"; case Token.SUB: return "-"; case Token.POS: return "+"; case Token.NEG: return "-"; case Token.ASSIGN: return "="; case Token.ASSIGN_BITOR: return "|="; case Token.ASSIGN_BITXOR: return "^="; case Token.ASSIGN_BITAND: return "&="; case Token.ASSIGN_LSH: return "<<="; case Token.ASSIGN_RSH: return ">>="; case Token.ASSIGN_URSH: return ">>>="; case Token.ASSIGN_ADD: return "+="; case Token.ASSIGN_SUB: return "-="; case Token.ASSIGN_MUL: return "*="; case Token.ASSIGN_DIV: return "/="; case Token.ASSIGN_MOD: return "%="; case Token.VOID: return "void"; case Token.TYPEOF: return "typeof"; case Token.INSTANCEOF: return "instanceof"; default: return null; } } /** * Converts an operator's token value (see {@link Token}) to a string * representation or fails. * * @param operator the operator's token value to convert * @return the string representation * @throws Error if the token value is not an operator */ static String opToStrNoFail(int operator) { String res = opToStr(operator); if (res == null) { throw new Error("Unknown op " + operator + ": " + Token.name(operator)); } return res; } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNodeType(type), traverseChildrenPred); } /** * @return true if n or any of its children are of the specified type */ static boolean contains

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Type(Node node, int type) { return containsType(node, type, Predicates.<Node>alwaysTrue()); } /** * Given a node tree, finds all the VAR declarations in that tree that are * not in an inner scope. Then adds a new VAR node at the top of the current * scope that redeclares them, if necessary. */ static void redeclareVarsInsideBranch(Node branch) { Collection<Node> vars = getVarsDeclaredInBranch(branch); if (vars.isEmpty()) { return; } Node parent = getAddingRoot(branch); for (Node nameNode : vars) { Node var = new Node( Token.VAR, Node.newString(Token.NAME, nameNode.getString()) .copyInformationFrom(nameNode)) .copyInformationFrom(nameNode); copyNameAnnotations(nameNode, var.getFirstChild()); parent.addChildToFront(var); } } /** * Copy any annotations that follow a named value. * @param source * @param destination */ static void copyNameAnnotations(Node source, Node destination) { if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) { destination.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } /** * Gets a Node at the top of the current scope where we can add new var * declarations as children. */ private static Node getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.getType() == Token.BLOCK || addingRoot.getType() == Token.SCRIPT); Preconditions.checkState(addingRoot.getFirstChild() == null || addingRoot.getFirstChild().getType() != Token.SCRIPT); return addingRoot; } /** Creates function name(params_0, ..., params_n) { body }. */ public static Node newFunctionNode(String name, List<Node> params, Node body, int lineno, int charno) { Node parameterParen = new Node(Token.LP, lineno, charno); for (Node param : params) { parameterParen.addChildToBack(param); } Node function = new Node(Token.FUNCTION, lineno, charno); function.addChildrenToBack( Node.newString(Token.NAME, name, lineno, charno)); function.addChildToBack(parameterParen); function.addChildToBack(body); return function; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.bar.baz") * @param lineno The source line offset. * @param charno The source character offset from start of the line. * @return A NAME or GETPROP node */ public static Node newQualifiedNameNode( CodingConvention convention, String name, int lineno, int charno) { int endPos = name.indexOf('.'); if (endPos == -1) { return newName(convention, name, lineno, charno); } Node node = newName( convention, name.substring(0, endPos), lineno, charno); int startPos; do { startPos = endPos + 1; endPos = name.indexOf('.', startPos); String part = (endPos == -1 ? name.substring(startPos) : name.substring(startPos, endPos)); Node propNode = Node.newString(Token.STRING, part, lineno, charno); if (convention.isConstantKey(part)) { propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } node = new Node(Token.GETPROP, node, propNode, lineno, charno); } while (endPos != -1); return node; } /** * Creates a node representing a qualified name, copying over the source * location information from the basis node and assigning the given original * name to the node. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @param basisNode The node that represents the name as currently found in * the AST. * @param originalName The original name of the item being represented by the * NAME node. Used for debugging information. * * @return A NAME or GETPROP node */ static Node newQualifiedNameNode( CodingConvention convention, String name, Node basisNode, String originalName) { Node node = newQualifiedNameNode(convention, name, -1, -1); setDebugInformation(node, basisNode, originalName); return node; } /** * Gets the root node of a qualified name. Must be either NAME or THIS. */ static Node getRootOfQualifiedName(Node qName) { for (Node current = qName; true; current = current.getFirstChild()) { int type = current.getType(); if (type == Token.NAME || type == Token.THIS) { return current; } Preconditions.checkState(type == Token.GETPROP); } } /** * Sets the debug information (source file info and orignal name) * on the given node. * * @param node The node on which to set the debug information. * @param basisNode The basis node from which to copy the source file info. * @param originalName The original name of the node. */ static void setDebugInformation(Node node, Node basisNode, String originalName) { node.copyInformationFromForTree(basisNode

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>); node.putProp(Node.ORIGINALNAME_PROP, originalName); } private static Node newName( CodingConvention convention, String name, int lineno, int charno) { Node nameNode = Node.newString(Token.NAME, name, lineno, charno); if (convention.isConstant(name)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } return nameNode; } /** * Creates a new node representing an *existing* name, copying over the source * location information from the basis node. * * @param name The name for the new NAME node. * @param basisNode The node that represents the name as currently found in * the AST. * * @return The node created. */ static Node newName( CodingConvention convention, String name, Node basisNode) { Node nameNode = Node.newString(Token.NAME, name); if (convention.isConstantKey(name)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } nameNode.copyInformationFrom(basisNode); return nameNode; } /** * Creates a new node representing an *existing* name, copying over the source * location information from the basis node and assigning the given original * name to the node. * * @param name The name for the new NAME node. * @param basisNode The node that represents the name as currently found in * the AST. * @param originalName The original name of the item being represented by the * NAME node. Used for debugging information. * * @return The node created. */ static Node newName( CodingConvention convention, String name, Node basisNode, String originalName) { Node nameNode = newName(convention, name, basisNode); nameNode.putProp(Node.ORIGINALNAME_PROP, originalName); return nameNode; } /** Test if all characters in the string are in the Basic Latin (aka ASCII) * character set - that they have UTF-16 values equal to or below 0x7f. * This check can find which identifiers with Unicode characters need to be * escaped in order to allow resulting files to be processed by non-Unicode * aware UNIX tools and editors. * * * See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode * for more on Basic Latin. * * @param s The string to be checked for ASCII-goodness. * * @return True if all characters in the string are in Basic Latin set. */ static boolean isLatin(String s) { char LARGEST_BASIC_LATIN = 0x7f; int len = s.length(); for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c > LARGEST_BASIC_LATIN) { return

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> false; } } return true; } /** * Determines whether the given name can appear on the right side of * the dot operator. Many properties (like reserved words) cannot. */ static boolean isValidPropertyName(String name) { return TokenStream.isJSIdentifier(name) && !TokenStream.isKeyword(name) && // no Unicode escaped characters - some browsers are less tolerant // of Unicode characters that might be valid according to the // language spec. // Note that by this point, unicode escapes have been converted // to UTF-16 characters, so we're only searching for character // values, not escapes. isLatin(name); } private static class VarCollector implements Visitor { final Map<String, Node> vars = Maps.newLinkedHashMap(); public void visit(Node n) { if (n.getType() == Token.NAME) { Node parent = n.getParent(); if (parent != null && parent.getType() == Token.VAR) { String name = n.getString(); if (!vars.containsKey(name)) { vars.put(name, n); } } } } } /** * Retrieves vars declared in the current node tree, excluding descent scopes. */ public static Collection<Node> getVarsDeclaredInBranch(Node root) { VarCollector collector = new VarCollector(); visitPreOrder( root, collector, new MatchNotFunction()); return collector.vars.values(); } /** * @return {@code true} if the node an assignment to a prototype property of * some constructor. */ static boolean isPrototypePropertyDeclaration(Node n) { if (!isExprAssign(n)) { return false; } return isPrototypeProperty(n.getFirstChild().getFirstChild()); } static boolean isPrototypeProperty(Node n) { String lhsString = n.getQualifiedName(); if (lhsString == null) { return false; } int prototypeIdx = lhsString.indexOf(".prototype."); return prototypeIdx != -1; } /** * @return The class name part of a qualified prototype name. */ static Node getPrototypeClassName(Node qName) { Node cur = qName; while (isGetProp(cur)) { if (cur.getLastChild().getString().equals("prototype")) { return cur.getFirstChild(); } else { cur = cur.getFirstChild(); } } return null; } /** * @return The string property name part of a qualified prototype name. */ static String getPrototypePropertyName(Node qName) { String qNameStr = qName.getQualifiedName(); int prototypeIdx = qNameStr.lastIndexOf(".prototype."); int memberIndex = prototypeIdx + ".prototype".length() + 1; return qNameStr.substring(memberIndex); } /** * Create a node for an empty result expression: * "void 0" */ static

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Node newUndefinedNode(Node srcReferenceNode) { // TODO(johnlenz): Why this instead of the more common "undefined"? Node node = new Node(Token.VOID, Node.newNumber(0)); if (srcReferenceNode != null) { node.copyInformationFromForTree(srcReferenceNode); } return node; } /** * Create a VAR node containing the given name and initial value expression. */ static Node newVarNode(String name, Node value) { Node nodeName = Node.newString(Token.NAME, name); if (value != null) { Preconditions.checkState(value.getNext() == null); nodeName.addChildToBack(value); nodeName.copyInformationFrom(value); } Node var = new Node(Token.VAR, nodeName) .copyInformationFrom(nodeName); return var; } /** * A predicate for matching name nodes with the specified node. */ private static class MatchNameNode implements Predicate<Node>{ final String name; MatchNameNode(String name){ this.name = name; } public boolean apply(Node n) { return n.getType() == Token.NAME && n.getString().equals(name); } } /** * A predicate for matching nodes with the specified type. */ static class MatchNodeType implements Predicate<Node>{ final int type; MatchNodeType(int type){ this.type = type; } public boolean apply(Node n) { return n.getType() == type; } } /** * A predicate for matching var or function declarations. */ static class MatchDeclaration implements Predicate<Node> { public boolean apply(Node n) { return isFunctionDeclaration(n) || n.getType() == Token.VAR; } } /** * A predicate for matching anything except function nodes. */ static class MatchNotFunction implements Predicate<Node>{ public boolean apply(Node n) { return !isFunction(n); } } /** * A predicate for matching statements without exiting the current scope. */ static class MatchShallowStatement implements Predicate<Node>{ public boolean apply(Node n) { Node parent = n.getParent(); return n.getType() == Token.BLOCK || (!isFunction(n) && (parent == null || isControlStructure(parent) || isStatementBlock(parent))); } } /** * Finds the number of times a type is referenced within the node tree. */ static int getNodeTypeReferenceCount( Node node, int type, Predicate<Node> traverseChildrenPred) { return getCount(node, new MatchNodeType(type), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNameNode(name), traverseChildrenPred); } /** * Whether a simple name

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name) { return isNameReferenced(node, name, Predicates.<Node>alwaysTrue()); } /** * Finds the number of times a simple name is referenced within the node tree. */ static int getNameReferenceCount(Node node, String name) { return getCount( node, new MatchNameNode(name), Predicates.<Node>alwaysTrue()); } /** * @return Whether the predicate is true for the node or any of its children. */ static boolean has(Node node, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { if (pred.apply(node)) { return true; } if (!traverseChildrenPred.apply(node)) { return false; } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { if (has(c, pred, traverseChildrenPred)) { return true; } } return false; } /** * @return The number of times the the predicate is true for the node * or any of its children. */ static int getCount( Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { int total = 0; if (pred.apply(n)) { total++; } if (traverseChildrenPred.apply(n)) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { total += getCount(c, pred, traverseChildrenPred); } } return total; } /** * Interface for use with the visit method. * @see #visit */ static interface Visitor { void visit(Node node); } /** * A pre-order traversal, calling Vistor.visit for each child matching * the predicate. */ static void visitPreOrder(Node node, Visitor vistor, Predicate<Node> traverseChildrenPred) { vistor.visit(node); if (traverseChildrenPred.apply(node)) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { visitPreOrder(c, vistor, traverseChildrenPred); } } } /** * A post-order traversal, calling Vistor.visit for each child matching * the predicate. */ static void visitPostOrder(Node node, Visitor vistor, Predicate<Node> traverseChildrenPred) { if (traverseChildrenPred.apply(node)) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { visitPostOrder(c, vistor, traverseChildrenPred); } } vistor.visit(node); } /** * @return Whether a TRY node has a finally block. */ static boolean hasFinally(Node n) { Preconditions.checkArgument(n.getType() == Token.TRY

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>); return n.getChildCount() == 3; } /** * @return The BLOCK node containing the CATCH node (if any) * of a TRY. */ static Node getCatchBlock(Node n) { Preconditions.checkArgument(n.getType() == Token.TRY); return n.getFirstChild().getNext(); } /** * @return Whether BLOCK (from a TRY node) contains a CATCH. * @see NodeUtil#getCatchBlock */ static boolean hasCatchHandler(Node n) { Preconditions.checkArgument(n.getType() == Token.BLOCK); return n.hasChildren() && n.getFirstChild().getType() == Token.CATCH; } /** * @param fnNode The function. * @return The Node containing the Function parameters. */ static Node getFnParameters(Node fnNode) { // Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION); return fnNode.getFirstChild().getNext(); } /** * Returns true if a name node represents a constant variable. * * <p>Determining whether a variable is constant has three steps: * <ol> * <li>In CodingConventionAnnotator, any name that matches the * {@link CodingConvention#isConstant(String)} is annotated with an * IS_CONSTANT_NAME property. * <li>The normalize pass renames any variable with the IS_CONSTANT_NAME * annotation and that is initialized to a constant value with * a variable name inlucding $$constant. * <li>Return true here if the variable includes $$constant in its name. * </ol> * * @param node A NAME or STRING node * @return True if the variable is constant */ static boolean isConstantName(Node node) { return node.getBooleanProp(Node.IS_CONSTANT_NAME); } /** Whether the given name is constant by coding convention. */ static boolean isConstantByConvention( CodingConvention convention, Node node, Node parent) { String name = node.getString(); if (parent.getType() == Token.GETPROP && node == parent.getLastChild()) { return convention.isConstantKey(name); } else if (isObjectLitKey(node, parent)) { return convention.isConstantKey(name); } else { return convention.isConstant(name); } } /** * @param nameNode A name node * @return The JSDocInfo for the name node */ static JSDocInfo getInfoForNameNode(Node nameNode) { JSDocInfo info = null; Node parent = null; if (nameNode != null) { info = nameNode.getJSDocInfo(); parent = nameNode.getParent(); } if (info == null && parent != null && ((parent.getType() == Token.VAR && parent.hasOneChild

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>()) || parent.getType() == Token.FUNCTION)) { info = parent.getJSDocInfo(); } return info; } /** * Get the JSDocInfo for a function. */ static JSDocInfo getFunctionInfo(Node n) { Preconditions.checkState(n.getType() == Token.FUNCTION); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.getType() == Token.ASSIGN) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.getType() == Token.NAME) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); n = n.getParent(); } return sourceName; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = isName(callTarget); Node call = new Node(Token.CALL, callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters) { call.addChildToBack(parameter); } return call; } /** * @return Whether the node is known to be a value that is not referenced * elsewhere. */ static boolean evaluatesToLocalValue(Node value) { return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse()); } /** * @param locals A predicate to apply to unknown local values. * @return Whether the node is known to be a value that is not a reference * outside the expression scope. */ static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) { switch (value.getType()) { case Token.ASSIGN: // A result that is aliased by a non-local name, is the effectively the // same as returning a non-local name, but this doesn't matter if the // value is immutable. return NodeUtil.isImmutableValue(value.getLastChild()) || (locals.apply(value) && evaluatesToLocalValue(value.getLastChild(), locals)); case Token.COMMA: return evaluatesToLocalValue(value.getLastChild(), locals); case Token.AND: case Token.OR: return evaluatesToLocalValue(value.getFirstChild(), locals) && evaluates

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ToLocalValue(value.getLastChild(), locals); case Token.HOOK: return evaluatesToLocalValue(value.getFirstChild().getNext(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.INC: case Token.DEC: if (value.getBooleanProp(Node.INCRDECR_PROP)) { return evaluatesToLocalValue(value.getFirstChild(), locals); } else { return true; } case Token.THIS: return locals.apply(value); case Token.NAME: return isImmutableValue(value) || locals.apply(value); case Token.GETELEM: case Token.GETPROP: // There is no information about the locality of object properties. return locals.apply(value); case Token.CALL: return callHasLocalResult(value) || isToStringMethodCall(value) || locals.apply(value); case Token.NEW: return true; case Token.FUNCTION: case Token.REGEXP: case Token.ARRAYLIT: case Token.OBJECTLIT: // Literals objects with non-literal children are allowed. return true; case Token.IN: // TODO(johnlenz): should IN operator be included in #isSimpleOperator? return true; default: // Other op force a local value: // x = '' + g (x is now an local string) // x -= g (x is now an local number) if (isAssignmentOp(value) || isSimpleOperator(value) || isImmutableValue(value)) { return true; } throw new IllegalStateException( "Unexpected expression node" + value + "\n parent:" + value.getParent()); } } /** * Given the first sibling, this returns the nth * sibling or null if no such sibling exists. * This is like "getChildAtIndex" but returns null for non-existent indexes. */ private static Node getNthSibling(Node first, int index) { Node sibling = first; while (index != 0 && sibling != null) { sibling = sibling.getNext(); index--; } return sibling; } /** * Given the function, this returns the nth * argument or null if no such parameter exists. */ static Node getArgumentForFunction(Node function, int index) { Preconditions.checkState(isFunction(function)); return getNthSibling( function.getFirstChild().getNext().getFirstChild(), index); } /** * Given the new or call, this returns the nth * argument of the call or null if no such argument exists. */ static Node getArgumentForCallOrNew(Node call, int index) { Preconditions.checkState(isCallOrNew(call)); return getNthSibling( call.getFirstChild().getNext(), index); } private static boolean isToStringMethodCall(Node call) { Node getNode = call.getFirstChild(); if (isGet(getNode)) { Node propNode = getNode.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { i++; n = n / 10; } while (n > 0); return i; } /** * Gets a string of spaces of the length specified. * @param sb The string builder to append to. * @param numSpaces The number of spaces in the string. */ @VisibleForTesting static void appendSpaces(StringBuilder sb, int numSpaces) { if (numSpaces > 16) { logger.warning("Tracer.appendSpaces called with large numSpaces"); // Avoid long loop in case some bug in the caller numSpaces = 16; } while (numSpaces >= 5) { sb.append(" "); numSpaces -= 5; } // We know it's less than 5 now switch (numSpaces) { case 1: sb.append(" "); break; case 2: sb.append(" "); break; case 3: sb.append(" "); break; case 4: sb.append(" "); break; } } /** * Adds a new tracing statistic to a trace * * @param tracingStatistic to enable a run * @return The index of this statistic (for use with stat.extraInfo()), or * -1 if the statistic is not enabled. */ static int addTracingStatistic(TracingStatistic tracingStatistic) { // Check to see if we can enable the tracing statistic before actually // adding it. if (tracingStatistic.enable()) { // No synchronization needed, since this is a copy-on-write array. extraTracingStatistics.add(tracingStatistic); // 99.9% of the time, this will be O(1) and return extraTracingStatistics.length - 1 return extraTracingStatistics.lastIndexOf(tracingStatistic); } else { return -1; } } /** * For testing purposes only. These removes all current tracers. Severe errors can occur * if there are any active tracers going on when this is called. * * The test suite uses this to remove any tracers that it has added. */ @VisibleForTesting static void clearTracingStatisticsTestingOnly() { extraTracingStatistics.clear(); } /** * Stop the trace. * This may only be done once and must be done from the same thread * that started it. * @param silence_threshold Traces for time less than silence_threshold * ms will be left out of the trace report. A value of -1 indicates * that the current ThreadTrace silence_threshold should be used. * @return The time that this trace actually ran */ long stop(int silence_threshold) { Preconditions.checkState(Thread.currentThread() == startThread); ThreadTrace trace = getThreadTrace(); // Do nothing if the thread trace was not initialized. if (!trace.isInitialized()) { return 0; } stopTimeMs = clock.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> sb.append(longToPaddedString(delta, digitsColWidth)); sb.append(" ms "); if (tracer.extraTracingValues != null) { for (int i = 0; i < tracer.extraTracingValues.length; i++) { delta = tracer.extraTracingValues[i]; sb.append(String.format("%4d", delta)); sb.append(extraTracingStatistics.get(i).getUnits()); sb.append("; "); } } } sb.append(indent); sb.append(tracer.toString()); return sb.toString(); } } /** Stores a thread's Trace */ static final class ThreadTrace { /** Events taking less than this number of milliseconds are not reported. */ int defaultSilenceThreshold; // non-final /** The Events corresponding to each startEvent/stopEvent */ final ArrayList<Event> events = new ArrayList<Event>(); /** Tracers that have not had their .stop() called */ final HashSet<Tracer> outstandingEvents = new HashSet<Tracer>(); /** Map from type to Stat object */ final Map<String, Stat> stats = new HashMap<String, Stat>(); /** * True if {@code outstandingEvents} has been cleared because we exceeded * the max trace limit. */ boolean isOutstandingEventsTruncated = false; /** * True if {@code events} has been cleared because we exceeded the max * trace limit. */ boolean isEventsTruncated = false; /** * Set to true if {@link Tracer#initCurrentThreadTrace()} was called by * the current thread. */ boolean isInitialized = false; /** * Whether pretty printing is enabled for the trace. */ boolean prettyPrint = false; /** Initialize the trace. */ void init() { isInitialized = true; } /** Is initialized? */ boolean isInitialized() { return isInitialized; } /** * Called by the constructor {@link Tracer#Tracer(String, String)} to create * a start event. */ void startEvent(Tracer t) { events.add(new Event(true, t)); boolean notAlreadyOutstanding = outstandingEvents.add(t); Preconditions.checkState(notAlreadyOutstanding); } /** * Called by {@link Tracer#stop()} to create a stop event. */ void endEvent(Tracer t, int silenceThreshold) { boolean wasOutstanding = outstandingEvents.remove(t); if (!wasOutstanding) { if (isOutstandingEventsTruncated) { // The events stack overflowed and was truncated, so just log a // warning. Otherwise, we get an exception which is extremely // confusing. logger.log(Level.WARNING, "event not found, probably because the event stack " + "overflowed and was truncated", new Throwable()); } else { // throw an exception if the event was not found and the events stack // is pristine throw new IllegalStateException(); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> long elapsed = t.stopTimeMs - t.startTimeMs; if (silenceThreshold == -1) { // use default silenceThreshold = defaultSilenceThreshold; } if (elapsed < silenceThreshold) { // If this one is silent then we need to remove the start Event boolean removed = false; for (int i = 0; i < events.size(); i++) { Event e = events.get(i); if (e.tracer == t) { Preconditions.checkState(e.isStart); events.remove(i); removed = true; break; } } // Only assert if we didn't find the original and the events // weren't truncated. Preconditions.checkState(removed || isEventsTruncated); } else { events.add(new Event(false, t)); } if (t.type != null) { Stat stat = stats.get(t.type); if (stat == null) { stat = new Stat(); if (!extraTracingStatistics.isEmpty()) { stat.extraInfo = new int[extraTracingStatistics.size()]; } stats.put(t.type, stat); } stat.count++; if (typeToCountMap != null) { typeToCountMap.incrementBy(t.type, 1); } stat.clockTime += elapsed; if (typeToTimeMap != null) { typeToTimeMap.incrementBy(t.type, elapsed); } if (stat.extraInfo != null && t.extraTracingValues != null) { int overlapLength = Math.min(stat.extraInfo.length, t.extraTracingValues.length); for (int i = 0; i < overlapLength; i++) { stat.extraInfo[i] += t.extraTracingValues[i]; AtomicTracerStatMap map = extraTracingStatistics.get(i).getTracingStat(); if (map != null) { map.incrementBy(t.type, t.extraTracingValues[i]); } } } if (elapsed < silenceThreshold) { stat.silent++; if (typeToSilentMap != null) { typeToSilentMap.incrementBy(t.type, 1); } } } } boolean isEmpty() { return events.size() == 0 && outstandingEvents.size() == 0; } void truncateOutstandingEvents() { isOutstandingEventsTruncated = true; outstandingEvents.clear(); } void truncateEvents() { isEventsTruncated = true; events.clear(); } /** Produces the lovely Trace seen in the class comments */ // Nullness checker does not understand that prettyPrint => indent != null @SuppressWarnings("nullness") @Override public String toString() { int numDigits = getMaxDigits(); StringBuilder sb = new StringBuilder(); long etime = -1; LinkedList<String> indent = prettyPrint ? new LinkedList<

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> {@link Candidate#canInline()} and finally perform inlining * using {@link Candidate#inlineVariable()}. * * The reason for the delayed evaluation of the candidates is because we * need two separate dataflow result. */ private final AbstractCompiler compiler; // These two pieces of data is persistent in the whole execution of enter // scope. private ControlFlowGraph<Node> cfg; private List<Candidate> candidates; private MustBeReachingVariableDef reachingDef; private MaybeReachingVariableUse reachingUses; private static final Predicate<Node> SIDE_EFFECT_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node n) { // When the node is null it means, we reached the implicit return // where the function returns (possibly without an return statement) if (n == null) { return false; } // TODO(user): We only care about calls to functions that // passes one of the dependent variable to a non-sideeffect free // function. if (NodeUtil.isCall(n) && NodeUtil.functionCallHasSideEffects(n)) { return true; } if (NodeUtil.isNew(n) && NodeUtil.constructorCallHasSideEffects(n)) { return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && apply(c)) { return true; } } return false; } }; public FlowSensitiveInlineVariables(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { return; // Don't even brother. All global variables are likely escaped. } // Compute the forward reaching definition. ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true); // Process the body of the function. Preconditions.checkState(NodeUtil.isFunction(t.getScopeRoot())); cfa.process(null, t.getScopeRoot().getLastChild()); cfg = cfa.getCfg(); reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler); reachingDef.analyze(); candidates = Lists.newLinkedList(); // Using the forward reaching definition search to find all the inline // candiates new NodeTraversal(compiler, new GatherCandiates()).traverse( t.getScopeRoot().getLastChild()); // Compute the backward reaching use. The CFG can be reused. reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler); reachingUses.analyze(); for (Candidate c : candidates) { if (c.canInline()) { c.inlineVariable(); } } } @Override public void exitScope(NodeTraversal t) {} @Override public void process(Node

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> externs, Node root) { (new NodeTraversal(compiler, this)).traverse(root); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(user): While the helpers do a subtree traversal on the AST, the // compiler pass itself only traverse the AST to look for function // declarations to perform dataflow analysis on. We could combine // the traversal in DataFlowAnalysis's computeEscaped later to save some // time. } /** * Gathers a list of possible candidates for inlining based only on * information from {@link MustBeReachingVariableDef}. The list will be stored * in {@code candidiates} and the validity of each inlining Candidate should * be later verified with {@link Candidate#canInline()} when * {@link MaybeReachingVariableUse} has been performed. */ private class GatherCandiates extends AbstractShallowCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { DiGraphNode<Node, Branch> graphNode = cfg.getDirectedGraphNode(n); if (graphNode == null) { // Not a CFG node. return; } FlowState<MustDef> state = graphNode.getAnnotation(); final MustDef defs = state.getIn(); final Node cfgNode = n; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isName(n)) { // Make sure that the name node is purely a read. if ((NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) || NodeUtil.isVar(parent) || parent.getType() == Token.INC || parent.getType() == Token.DEC || parent.getType() == Token.LP || parent.getType() == Token.CATCH) { return; } String name = n.getString(); if (compiler.getCodingConvention().isExported(name)) { return; } Node defNode = reachingDef.getDef(name, cfgNode); if (defNode != null && !reachingDef.dependsOnOuterScopeVars(name, cfgNode)) { candidates.add(new Candidate(name, defNode, n, cfgNode)); } } } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Models the connection between a definition and a use of that definition. */ private class Candidate { // Name of the variable. private final String varName; // Nodes related to the definition. private Node def; private final Node defCfgNode; // Nodes related to the use. private final Node use; private final Node useCfgNode; // Number of uses of the variable within the CFG node that represented the // use in the CFG. private int numUseWithinUse

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>CfgNode; Candidate(String varName, Node defCfgNode, Node use, Node useCfgNode) { Preconditions.checkArgument(NodeUtil.isName(use)); this.varName = varName; this.defCfgNode = defCfgNode; this.use = use; this.useCfgNode = useCfgNode; } private boolean canInline() { // Cannot inline a parameter. if (NodeUtil.isFunction(defCfgNode)) { return false; } getDefinition(defCfgNode, null); getNumUseInUseCfgNode(useCfgNode, null); // Definition was not found. if (def == null) { return false; } // Check that the assignment isn't used as a R-Value. // TODO(user): Certain cases we can still inline. if (NodeUtil.isAssign(def) && !NodeUtil.isExprAssign(def.getParent())) { return false; } // The right of the definition has side effect: // Example, for x: // x = readProp(b), modifyProp(b); print(x); if (checkRightOf(def, defCfgNode, SIDE_EFFECT_PREDICATE)) { return false; } // Similar check as the above but this time, all the sub-expressions // left of the use of the variable. // x = readProp(b); modifyProp(b), print(x); if (checkLeftOf(use, useCfgNode, SIDE_EFFECT_PREDICATE)) { return false; } // Similar side effect check as above but this time the side effect is // else where along the path. // x = readProp(b); while(modifyProp(b)) {}; print(x); CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> pathCheck = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>( cfg, cfg.getDirectedGraphNode(defCfgNode), cfg.getDirectedGraphNode(useCfgNode), SIDE_EFFECT_PREDICATE, Predicates. <DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(), false); if (pathCheck.somePathsSatisfyPredicate()) { return false; } // TODO(user): Side-effect is ok sometimes. As long as there are no // side-effect function down all paths to the use. Once we have all the // side-effect analysis tool. if (NodeUtil.mayHaveSideEffects(def.getLastChild())) { return false; } // TODO(user): We could inline all the uses if the expression is short. // Finally we have to make sure that there are no more than one use // in the program and in the CFG node. Even when it is semantically // correctly inlining twice increases code size. if (numUseWithinUseCfgNode != 1) { return false; } // Make sure that the name is not

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> within a loop if (NodeUtil.isWithinLoop(use)) { return false; } // We give up inling stuff with R-Value that has GETPROP, GETELEM, // or anything that creates a new object. // Example: // var x = a.b.c; j.c = 1; print(x); // Inlining print(a.b.c) is not safe consider j and be alias to a.b. // TODO(user): We could get more accuracy by looking more in-detail // what j is and what x is trying to into to. if (NodeUtil.has(def.getLastChild(), new Predicate<Node>() { @Override public boolean apply(Node input) { switch (input.getType()) { case Token.GETELEM: case Token.GETPROP: case Token.ARRAYLIT: case Token.OBJECTLIT: case Token.REGEXP: case Token.NEW: return true; } return false; } }, new Predicate<Node>() { @Override public boolean apply(Node input) { // Recurse if the node is not a function. return !NodeUtil.isFunction(input); } })) { return false; } Collection<Node> uses = reachingUses.getUses(varName, defCfgNode); if (uses.size() != 1) { return false; } return true; } /** * Actual transformation. */ private void inlineVariable() { Node defParent = def.getParent(); Node useParent = use.getParent(); if (NodeUtil.isAssign(def)) { Node rhs = def.getLastChild(); rhs.detachFromParent(); // Oh yes! I have grandparent to remove this. Preconditions.checkState(NodeUtil.isExpressionNode(defParent)); while (defParent.getParent().getType() == Token.LABEL) { defParent = defParent.getParent(); } defParent.detachFromParent(); useParent.replaceChild(use, rhs); } else if (NodeUtil.isVar(defParent)) { Node rhs = def.getLastChild(); def.removeChild(rhs); useParent.replaceChild(use, rhs); } else { Preconditions.checkState(false, "No other definitions can be inlined."); } compiler.reportCodeChange(); } /** * Set the def node * * @param n A node that has a corresponding CFG node in the CFG. */ private void getDefinition(Node n, Node parent) { AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: if (n.getString().equals(varName) && n.hasChildren()) { def = n; } return; case Token.ASSIGN: Node lhs = n

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.AbstractCompiler; import com.google.javascript.jscomp.CompilerPass; import com.google.javascript.jscomp.NodeTraversal; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.NodeUtil; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; /** * Transform the structure of the AST so that the number of explicit exits * are minimized. * * @author johnlenz@google.com (John Lenz) */ class MinimizeExitPoints extends AbstractPostOrderCallback implements CompilerPass { AbstractCompiler compiler; MinimizeExitPoints(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.LABEL: tryMinimizeExits( n.getLastChild(), Token.BREAK, n.getFirstChild().getString()); break; case Token.FOR: case Token.WHILE: tryMinimizeExits( NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null); break; case Token.DO: tryMinimizeExits( NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null); Node cond = NodeUtil.getConditionExpression(n); if (NodeUtil.getBooleanValue(cond) == TernaryValue.FALSE) { // Normally, we wouldn't be able to optimize BREAKs inside a loop // but as we know the condition will always false, we can treat them // as we would a CONTINUE. tryMinimizeExits( n.getFirstChild(), Token.BREAK, null); } break; case Token.FUNCTION: tryMinimizeExits( n.getLastChild(), Token.RETURN, null); break; } } /**

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * Attempts to minimize the number of explicit exit points in a control * structure to take advantage of the implied exit at the end of the * structure. This is accomplished by removing redundant statements, and * moving statements following a qualifying IF node into that node. * For example: * * function () { * if (x) return; * else blah(); * foo(); * } * * becomes: * * function () { * if (x) ; * else { * blah(); * foo(); * } * * @param n The execution node of a parent to inspect. * @param exitType The type of exit to look for. * @param labelName If parent is a label the name of the label to look for, * null otherwise. * @nullable labelName non-null only for breaks within labels. */ void tryMinimizeExits(Node n, int exitType, String labelName) { // Just an 'exit'. if (matchingExitNode(n, exitType, labelName)) { NodeUtil.removeChild(n.getParent(), n); compiler.reportCodeChange(); return; } // Just an 'if'. if (n.getType() == Token.IF) { Node ifBlock = n.getFirstChild().getNext(); tryMinimizeExits(ifBlock, exitType, labelName); Node elseBlock = ifBlock.getNext(); if (elseBlock != null) { tryMinimizeExits(elseBlock, exitType, labelName); } return; } // Just a 'try/catch/finally'. if (n.getType() == Token.TRY) { Node tryBlock = n.getFirstChild(); tryMinimizeExits(tryBlock, exitType, labelName); Node allCatchNodes = NodeUtil.getCatchBlock(n); if (NodeUtil.hasCatchHandler(allCatchNodes)) { Preconditions.checkState(allCatchNodes.hasOneChild()); Node catchNode = allCatchNodes.getFirstChild(); Node catchCodeBlock = catchNode.getLastChild(); tryMinimizeExits(catchCodeBlock, exitType, labelName); } if (NodeUtil.hasFinally(n)) { Node finallyBlock = n.getLastChild(); tryMinimizeExits(finallyBlock, exitType, labelName); } } // Just a 'label'. if (n.getType() == Token.LABEL) { Node labelBlock = n.getLastChild(); tryMinimizeExits(labelBlock, exitType, labelName); } // TODO(johnlenz): The last case of SWITCH statement? // The rest assumes a block with at least one child, bail on anything else. if (n.getType() != Token.BLOCK || n.getLastChild() == null) { return; } // Multiple if-exits can be converted in a single pass. // Convert

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> "if (blah) break; if (blah2) break; other_stmt;" to // become "if (blah); else { if (blah2); else { other_stmt; } }" // which will get converted to "if (!blah && !blah2) { other_stmt; }". for (Node c : n.children()) { // An 'if' block to process below. if (c.getType() == Token.IF) { Node ifTree = c; Node trueBlock, falseBlock; // First, the true condition block. trueBlock = ifTree.getFirstChild().getNext(); falseBlock = trueBlock.getNext(); tryMinimizeIfBlockExits(trueBlock, falseBlock, ifTree, exitType, labelName); // Now the else block. // The if blocks may have changed, get them again. trueBlock = ifTree.getFirstChild().getNext(); falseBlock = trueBlock.getNext(); if (falseBlock != null) { tryMinimizeIfBlockExits(falseBlock, trueBlock, ifTree, exitType, labelName); } } if (c == n.getLastChild()) { break; } } // Now try to minimize the exits of the last child, if it is removed // look at what has become the last child. for (Node c = n.getLastChild(); c != null; c = n.getLastChild()) { tryMinimizeExits(c, exitType, labelName); // If the node is still the last child, we are done. if (c == n.getLastChild()) { break; } } } /** * Look for exits (returns, breaks, or continues, depending on the context) at * the end of a block and removes them by moving the if node's siblings, * if any, into the opposite condition block. * * @param srcBlock The block to inspect. * @param destBlock The block to move sibling nodes into. * @param ifNode The if node to work with. * @param exitType The type of exit to look for. * @param labelName The name associated with the exit, if any. * @nullable labelName null for anything excepted for named-break associated * with a label. */ private void tryMinimizeIfBlockExits(Node srcBlock, Node destBlock, Node ifNode, int exitType, String labelName) { Node exitNodeParent = null; Node exitNode = null; // Pick an exit node candidate. if (srcBlock.getType() == Token.BLOCK) { if (!srcBlock.hasChildren()) { return; } exitNodeParent = srcBlock; exitNode = exitNodeParent.getLastChild(); } else { // Just a single statement, if it isn't an exit bail. exitNodeParent = ifNode; exitNode = srcBlock; } // Verify the candidate. if (!matchingExitNode(exit

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Node, exitType, labelName)) { return; } // Take case of the if nodes siblings, if any. if (ifNode.getNext() != null) { // Move siblings of the if block into the opposite // logic block of the exit. Node newDestBlock = new Node(Token.BLOCK).copyInformationFrom(ifNode); if (destBlock == null) { // Only possible if this is the false block. ifNode.addChildToBack(newDestBlock); } else if (destBlock.getType() == Token.EMPTY) { // Use the new block. ifNode.replaceChild(destBlock, newDestBlock); } else if (destBlock.getType() == Token.BLOCK) { // Reuse the existing block. newDestBlock = destBlock; } else { // Add the existing statement to the new block. ifNode.replaceChild(destBlock, newDestBlock); newDestBlock.addChildToBack(destBlock); } // Move all the if node's following siblings. moveAllFollowing(ifNode, ifNode.getParent(), newDestBlock); } // Get rid of the "exit", replace with an empty item if needed. NodeUtil.removeChild(exitNodeParent, exitNode); compiler.reportCodeChange(); } /** * Determines if n matches the type and name for the following types of * "exits": * - return without values * - continues and breaks with or without names. * @param n The node to inspect. * @param type The Token type to look for. * @param labelName The name that must be associated with the exit type. * @nullable labelName non-null only for breaks associated with labels. * @return Whether the node matches the specified block-exit type. */ static private boolean matchingExitNode(Node n, int type, String labelName) { if (n.getType() == type) { if (type == Token.RETURN) { // only returns without expressions. return !n.hasChildren(); } else { if (labelName == null) { return !n.hasChildren(); } else { return n.hasChildren() && labelName.equals(n.getFirstChild().getString()); } } } return false; } /** * Move all the child nodes following start in srcParent to the end of * destParent's child list. * @param start The start point in the srcParent child list. * @param srcParent The parent node of start. * @param destParent The destination node. */ static private void moveAllFollowing( Node start, Node srcParent, Node destParent) { for (Node n = start.getNext(); n != null; n = start.getNext()) { boolean isFunctionDeclaration = NodeUtil.isFunctionDeclaration(n); srcParent.removeChild(n); if (isFunctionDeclaration) { destParent.addChildToFront(n);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeUtil.MatchNotFunction; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; /** * CodeGenerator generates codes from a parse tree, sending it to the specified * CodeConsumer. * */ class CodeGenerator { private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final CodeConsumer cc; private final CharsetEncoder outputCharsetEncoder; CodeGenerator( CodeConsumer consumer, Charset outputCharset) { cc = consumer; if (outputCharset == null || outputCharset == Charsets.US_ASCII) { // If we want our default (pretending to be UTF-8, but escaping anything // outside of straight ASCII), then don't use the encoder, but // just special-case the code. This keeps the normal path through // the code identical to how it's been for years. this.outputCharsetEncoder = null; } else { this.outputCharsetEncoder = outputCharset.newEncoder(); } } CodeGenerator(CodeConsumer consumer) { this(consumer, null); } void add(String str) { cc.add(str); } private void addIdentifier(String identifier) { cc.addIdentifier(identifierEscape(identifier)); } void add(Node n) { add(n, Context.OTHER); } void add(Node n, Context context) { if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); addLeftExpr(first, p, context); cc.addOp(opstr, true); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); // Handle associativity. // e.g. if the parse tree is a * (b * c), // we can simply generate a * b * c. if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { // Assignments are the only right-associative binary operators addExpr(last, p, rhsContext); } else { addExpr(last, p + 1, rhsContext); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { Preconditions.checkState(first.getNext().getType() == Token.BLOCK && !first.getNext().hasMoreThanOneChild()); Preconditions.checkState(childCount >= 2 && childCount <= 3); add("try"); add(first, Context.PRESERVE_BLOCK); // second child contains the catch block, or nothing if there // isn't a catch block Node catchblock = first.getNext().getFirstChild(); if (catchblock != null) { add(catchblock); } if (childCount == 3) { add("finally"); add(last, Context.PRESERVE_BLOCK); } break; } case Token.CATCH: Preconditions.checkState(childCount == 2); add("catch("); add(first); add(")"); add(last, Context.PRESERVE_BLOCK); break; case Token.THROW: Preconditions.checkState(childCount == 1); add("throw"); add(first); // Must have a ';' after a throw statement, otherwise safari can't // parse this. cc.endStatement(true); break; case Token.RETURN: add("return"); if (childCount == 1) { add(first); } else { Preconditions.checkState(childCount == 0); } cc.endStatement(); break; case Token.VAR: if (first != null) { add("var "); addList(first, false, getContextForNoInOperator(context)); } break; case Token.LABEL_NAME: Preconditions.checkState(!n.getString().isEmpty()); addIdentifier(n.getString

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>()); break; case Token.NAME: if (first == null || first.getType() == Token.EMPTY) { addIdentifier(n.getString()); } else { Preconditions.checkState(childCount == 1); addIdentifier(n.getString()); cc.addOp("=", true); if (first.getType() == Token.COMMA) { addExpr(first, NodeUtil.precedence(Token.ASSIGN)); } else { // Add expression, consider nearby code at lowest level of // precedence. addExpr(first, 0, getContextForNoInOperator(context)); } } break; case Token.ARRAYLIT: add("["); addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP)); add("]"); break; case Token.LP: add("("); addList(first); add(")"); break; case Token.COMMA: Preconditions.checkState(childCount == 2); addList(first, false, context); break; case Token.NUMBER: Preconditions.checkState( childCount == ((n.getParent() != null && n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)); cc.addNumber(n.getDouble()); break; case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: { // All of these unary operators are right-associative Preconditions.checkState(childCount == 1); cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type)); break; } case Token.HOOK: { Preconditions.checkState(childCount == 3); int p = NodeUtil.precedence(type); addLeftExpr(first, p + 1, context); cc.addOp("?", true); addExpr(first.getNext(), 1); cc.addOp(":", true); addExpr(last, 1); break; } case Token.REGEXP: if (first.getType() != Token.STRING || last.getType() != Token.STRING) { throw new Error("Expected children to be strings"); } String regexp = regexpEscape(first.getString(), outputCharsetEncoder); // I only use one .add because whitespace matters if (childCount == 2) { add(regexp + last.getString()); } else { Preconditions.checkState(childCount == 1); add(regexp); } break; case Token.GET_REF: add(first); break; case Token.REF_SPECIAL: Preconditions.checkState(childCount == 1); add(first); add("."); add((String) n.getProp(Node.NAME_PROP)); break; case Token.FUNCTION:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } Preconditions.checkState(childCount == 3); boolean funcNeedsParens = (context == Context.START_OF_EXPR); if (funcNeedsParens) { add("("); } add("function"); add(first); add(first.getNext()); add(last, Context.PRESERVE_BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.GET: case Token.SET: Preconditions.checkState(n.getParent().getType() == Token.OBJECTLIT); Preconditions.checkState(childCount == 1); Preconditions.checkState(first.getType() == Token.FUNCTION); // Get methods are unnamed Preconditions.checkState(first.getFirstChild().getString().isEmpty()); if (type == Token.GET) { // Get methods have no parameters. Preconditions.checkState(!first.getChildAtIndex(1).hasChildren()); add("get "); } else { // Set methods have one parameter. Preconditions.checkState(first.getChildAtIndex(1).hasOneChild()); add("set "); } // The name is on the GET or SET node. String name = n.getString(); Node fn = first; Node parameters = fn.getChildAtIndex(1); Node body = fn.getLastChild(); // Add the property name. if (TokenStream.isJSIdentifier(name) && // do not encode literally any non-literal characters that were // unicode escaped. NodeUtil.isLatin(name)) { add(name); } else { add(jsString(n.getString(), outputCharsetEncoder)); } add(parameters); add(body, Context.PRESERVE_BLOCK); break; case Token.SCRIPT: case Token.BLOCK: { if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } boolean preserveBlock = context == Context.PRESERVE_BLOCK; if (preserveBlock) { cc.beginBlock(); } boolean preferLineBreaks = type == Token.SCRIPT || (type == Token.BLOCK && !preserveBlock && n.getParent() != null && n.getParent().getType() == Token.SCRIPT); for (Node c = first; c != null; c = c.getNext()) { add(c, Context.STATEMENT); // VAR doesn't include ';' since it gets used in expressions if (c.getType() == Token.VAR) { cc.endStatement(); } if (c.getType() == Token.FUNCTION) { cc.maybeLineBreak(); } // Prefer to break lines in between top-level statements // because top level statements are more homogeneous. if (preferLineBreaks) { cc.notePreferredLineBreak(); }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } if (preserveBlock) { cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } break; } case Token.FOR: if (childCount == 4) { add("for("); if (first.getType() == Token.VAR) { add(first, Context.IN_FOR_INIT_CLAUSE); } else { addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE); } add(";"); add(first.getNext()); add(";"); add(first.getNext().getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { Preconditions.checkState(childCount == 3); add("for("); add(first); add("in"); add(first.getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } break; case Token.DO: Preconditions.checkState(childCount == 2); add("do"); addNonEmptyStatement(first, Context.OTHER, false); add("while("); add(last); add(")"); cc.endStatement(); break; case Token.WHILE: Preconditions.checkState(childCount == 2); add("while("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.EMPTY: Preconditions.checkState(childCount == 0); break; case Token.GETPROP: { Preconditions.checkState( childCount == 2, "Bad GETPROP: expected 2 children, but got %s", childCount); Preconditions.checkState( last.getType() == Token.STRING, "Bad GETPROP: RHS should be STRING"); boolean needsParens = (first.getType() == Token.NUMBER); if (needsParens) { add("("); } addLeftExpr(first, NodeUtil.precedence(type), context); if (needsParens) { add(")"); } add("."); addIdentifier(last.getString()); break; } case Token.GETELEM: Preconditions.checkState( childCount == 2, "Bad GETELEM: expected 2 children but got %s", childCount); addLeftExpr(first, NodeUtil.precedence(type), context); add("["); add(first.getNext()); add("]"); break; case Token.WITH: Preconditions.checkState(childCount == 2); add("with("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.INC: case Token.DEC: { Preconditions.checkState(childCount == 1);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> String o = type == Token.INC ? "++" : "--"; int postProp = n.getIntProp(Node.INCRDECR_PROP); // A non-zero post-prop value indicates a post inc/dec, default of zero // is a pre-inc/dec. if (postProp != 0) { addLeftExpr(first, NodeUtil.precedence(type), context); cc.addOp(o, false); } else { cc.addOp(o, false); add(first); } break; } case Token.CALL: // We have two special cases here: // 1) If the left hand side of the call is a direct reference to eval, // then it must have a DIRECT_EVAL annotation. If it does not, then // that means it was originally an indirect call to eval, and that // indirectness must be preserved. // 2) If the left hand side of the call is a property reference, // then the call must not a FREE_CALL annotation. If it does, then // that means it was originally an call without an explicit this and // that must be preserved. if (isIndirectEval(first) || n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) { add("(0,"); addExpr(first, NodeUtil.precedence(Token.COMMA)); add(")"); } else { addLeftExpr(first, NodeUtil.precedence(type), context); } add("("); addList(first.getNext()); add(")"); break; case Token.IF: boolean hasElse = childCount == 3; boolean ambiguousElseClause = context == Context.BEFORE_DANGLING_ELSE && !hasElse; if (ambiguousElseClause) { cc.beginBlock(); } add("if("); add(first); add(")"); if (hasElse) { addNonEmptyStatement( first.getNext(), Context.BEFORE_DANGLING_ELSE, false); add("else"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { addNonEmptyStatement(first.getNext(), Context.OTHER, false); Preconditions.checkState(childCount == 2); } if (ambiguousElseClause) { cc.endBlock(); } break; case Token.NULL: case Token.THIS: case Token.FALSE: case Token.TRUE: Preconditions.checkState(childCount == 0); add(Node.tokenToName(type)); break; case Token.CONTINUE: Preconditions.checkState(childCount <= 1); add("continue"); if (childCount == 1) { if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } cc.endStatement(); break; case Token.DEBUGGER: Preconditions.checkState(childCount == 0); add("debugger"); cc.endStatement(); break; case Token.BREAK: Preconditions.checkState(childCount <= 1); add("break"); if (childCount == 1) { if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first); } cc.endStatement(); break; case Token.EXPR_VOID: throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT."); case Token.EXPR_RESULT: Preconditions.checkState(childCount == 1); add(first, Context.START_OF_EXPR); cc.endStatement(); break; case Token.NEW: add("new "); int precedence = NodeUtil.precedence(type); // If the first child contains a CALL, then claim higher precedence // to force parentheses. Otherwise, when parsed, NEW will bind to the // first viable parentheses (don't traverse into functions). if (NodeUtil.containsType(first, Token.CALL, new MatchNotFunction())) { precedence = NodeUtil.precedence(first.getType()) + 1; } addExpr(first, precedence); // '()' is optional when no arguments are present Node next = first.getNext(); if (next != null) { add("("); addList(next); add(")"); } break; case Token.STRING: if (childCount != ((n.getParent() != null && n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)) { throw new IllegalStateException( "Unexpected String children: " + n.getParent().toStringTree()); } add(jsString(n.getString(), outputCharsetEncoder)); break; case Token.DELPROP: Preconditions.checkState(childCount == 1); add("delete "); add(first); break; case Token.OBJECTLIT: { boolean needsParens = (context == Context.START_OF_EXPR); if (needsParens) { add("("); } add("{"); for (Node c = first; c != null; c = c.getNext()) { if (c != first) { cc.listSeparator(); } if (c.getType() == Token.GET || c.getType() == Token.SET) { add(c); } else { // Object literal property names don't have to be quoted if they are // not JavaScript keywords if (c.getType() == Token.STRING && !TokenStream.isKeyword(c.getString()) && TokenStream.isJSIdentifier(c.getString()) && // do not encode literally any non-literal characters that were // unicode escaped. NodeUtil.isLatin(c.getString

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>())) { add(c.getString()); } else { addExpr(c, 1); } add(":"); addExpr(c.getFirstChild(), 1); } } add("}"); if (needsParens) { add(")"); } break; } case Token.SWITCH: add("switch("); add(first); add(")"); cc.beginBlock(); addAllSiblings(first.getNext()); cc.endBlock(context == Context.STATEMENT); break; case Token.CASE: Preconditions.checkState(childCount == 2); add("case "); add(first); addCaseBody(last); break; case Token.DEFAULT: Preconditions.checkState(childCount == 1); add("default"); addCaseBody(first); break; case Token.LABEL: Preconditions.checkState(childCount == 2); if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(first); add(":"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), true); break; // This node is auto generated in anonymous functions and should just get // ignored for our purposes. case Token.SETNAME: break; default: throw new Error("Unknown type " + type + "\n" + n.toStringTree()); } cc.endSourceMapping(n); } /** * @return Whether the name is an indirect eval. */ private boolean isIndirectEval(Node n) { return n.getType() == Token.NAME && "eval".equals(n.getString()) && !n.getBooleanProp(Node.DIRECT_EVAL); } /** * Adds a block or expression, substituting a VOID with an empty statement. * This is used for "for (...);" and "if (...);" type statements. * * @param n The node to print. * @param context The context to determine how the node should be printed. */ private void addNonEmptyStatement( Node n, Context context, boolean allowNonBlockChild) { Node nodeToProcess = n; if (!allowNonBlockChild && n.getType() != Token.BLOCK) { throw new Error("Missing BLOCK child."); } // Strip unneeded blocks, that is blocks with <2 children unless // the CodePrinter specifically wants to keep them. if (n.getType() == Token.BLOCK) { int count = getNonEmptyChildCount(n, 2); if (count == 0) { if (cc.shouldPreserveExtraBlocks()) { cc.beginBlock(); cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } else { cc.endStatement(true); } return; } if (count == 1) { // Hack around a couple of browser bugs:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // Safari needs a block around function declarations. // IE6/7 needs a block around DOs. Node firstAndOnlyChild = getFirstNonEmptyChild(n); boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks(); if (alwaysWrapInBlock || isOneExactlyFunctionOrDo(firstAndOnlyChild)) { cc.beginBlock(); add(firstAndOnlyChild, Context.STATEMENT); cc.maybeLineBreak(); cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); return; } else { // Continue with the only child. nodeToProcess = firstAndOnlyChild; } } if (count > 1) { context = Context.PRESERVE_BLOCK; } } if (nodeToProcess.getType() == Token.EMPTY) { cc.endStatement(true); } else { add(nodeToProcess, context); // VAR doesn't include ';' since it gets used in expressions - so any // VAR in a statement context needs a call to endStatement() here. if (nodeToProcess.getType() == Token.VAR) { cc.endStatement(); } } } /** * @return Whether the Node is a DO or FUNCTION (with or without * labels). */ private boolean isOneExactlyFunctionOrDo(Node n) { if (n.getType() == Token.LABEL) { Node labeledStatement = n.getLastChild(); if (labeledStatement.getType() != Token.BLOCK) { return isOneExactlyFunctionOrDo(labeledStatement); } else { // For labels with block children, we need to ensure that a // labeled FUNCTION or DO isn't generated when extraneous BLOCKs // are skipped. if (getNonEmptyChildCount(n, 2) == 1) { return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n)); } else { // Either a empty statement or an block with more than one child, // way it isn't a FUNCTION or DO. return false; } } } else { return (n.getType() == Token.FUNCTION || n.getType() == Token.DO); } } /** * Adds a node at the left-hand side of an expression. Unlike * {@link #addExpr(Node,int)}, this preserves information about the context. * * The left side of an expression is special because in the JavaScript * grammar, certain tokens may be parsed differently when they are at * the beginning of a statement. For example, "{}" is parsed as a block, * but "{'x': 'y'}" is parsed as an object literal. */ void addLeftExpr(Node n, int minPrecedence, Context context) { addExpr(n, minPrecedence, context); } void addExpr(Node n, int minPrecedence) { addExpr(n, minPrecedence, Context.OTHER); } private

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> void addExpr(Node n, int minPrecedence, Context context) { if ((NodeUtil.precedence(n.getType()) < minPrecedence) || ((context == Context.IN_FOR_INIT_CLAUSE) && (n.getType() == Token.IN))){ add("("); add(n, clearContextForNoInOperator(context)); add(")"); } else { add(n, context); } } void addList(Node firstInList) { addList(firstInList, true, Context.OTHER); } void addList(Node firstInList, boolean isArrayOrFunctionArgument) { addList(firstInList, isArrayOrFunctionArgument, Context.OTHER); } void addList(Node firstInList, boolean isArrayOrFunctionArgument, Context lhsContext) { for (Node n = firstInList; n != null; n = n.getNext()) { boolean isFirst = n == firstInList; if (isFirst) { addLeftExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext); } else { cc.listSeparator(); addExpr(n, isArrayOrFunctionArgument ? 1 : 0); } } } /** * This function adds a comma-separated list as is specified by an ARRAYLIT * node with the associated skipIndexes array. This is a space optimization * since we avoid creating a whole Node object for each empty array literal * slot. * @param firstInList The first in the node list (chained through the next * property). * @param skipIndexes If not null, then the array of skipped entries in the * array. */ void addList(Node firstInList, int[] skipIndexes) { int nextSlot = 0; int nextSkipSlot = 0; for (Node n = firstInList; n != null; n = n.getNext()) { while (skipIndexes != null && nextSkipSlot < skipIndexes.length) { if (nextSlot == skipIndexes[nextSkipSlot]) { cc.listSeparator(); nextSlot++; nextSkipSlot++; } else { break; } } if (n != firstInList) { cc.listSeparator(); } addExpr(n, 1); nextSlot++; } } void addCaseBody(Node caseBody) { cc.beginCaseBody(); add(caseBody); cc.endCaseBody(); } void addAllSiblings(Node n) { for (Node c = n; c != null; c = c.getNext()) { add(c); } } /** Outputs a js string, using the optimal (single/double) quote character */ static String jsString(String s, CharsetEncoder outputCharsetEncoder) { int singleq = 0, doubleq = 0; // could count the quotes and pick the optimal quote character for (int i = 0; i

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> we're given an outputCharsetEncoder, then check if the // character can be represented in this character set. if (outputCharsetEncoder != null) { if (outputCharsetEncoder.canEncode(c)) { sb.append(c); } else { // Unicode-escape the character. appendHexJavaScriptRepresentation(sb, c); } } else { // No charsetEncoder provided - pass straight latin characters // through, and escape the rest. Doing the explicit character // check is measurably faster than using the CharsetEncoder. if (c > 0x1f && c <= 0x7f) { sb.append(c); } else { // Other characters can be misinterpreted by some js parsers, // or perhaps mangled by proxies along the way, // so we play it safe and unicode escape them. appendHexJavaScriptRepresentation(sb, c); } } } } sb.append(quote); return sb.toString(); } static String identifierEscape(String s) { // First check if escaping is needed at all -- in most cases it isn't. if (NodeUtil.isLatin(s)) { return s; } // Now going through the string to escape non-latin characters if needed. StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // Identifiers should always go to Latin1/ ASCII characters because // different browser's rules for valid identifier characters are // crazy. if (c > 0x1F && c < 0x7F) { sb.append(c); } else { appendHexJavaScriptRepresentation(sb, c); } } return sb.toString(); } /** * @param maxCount The maximum number of children to look for. * @return The number of children of this node that are non empty up to * maxCount. */ private static int getNonEmptyChildCount(Node n, int maxCount) { int i = 0; Node c = n.getFirstChild(); for (; c != null && i < maxCount; c = c.getNext()) { if (c.getType() == Token.BLOCK) { i += getNonEmptyChildCount(c, maxCount-i); } else if (c.getType() != Token.EMPTY) { i++; } } return i; } /** Gets the first non-empty child of the given node. */ private static Node getFirstNonEmptyChild(Node n) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.getType() == Token.BLOCK) { Node result = getFirstNonEmptyChild(c); if (result != null) { return result; } } else if (c.getType() != Token.EMPTY) { return c; }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * The variable's doc info. */ private JSDocInfo info = null; /** * Whether the variable's type has been inferred or is declared. An inferred * type may change over time (as more code is discovered), whereas a * declared type is a static contract that must be matched. */ private final boolean typeInferred; /** Input source */ CompilerInput input; /** Whether the variable is a define */ boolean isDefine; /** * The index at which the var is declared. e..g if it's 0, it's the first * declared variable in that scope */ int index; /** The enclosing scope */ Scope scope; /** * Creates a variable. * * @param inferred whether its type is inferred (as opposed to declared) */ private Var(boolean inferred) { this.typeInferred = inferred; } /** * Gets the name of the variable. */ public String getName() { return name; } /** * Gets the parent of the name node. */ public Node getParentNode() { return nameNode == null ? null : nameNode.getParent(); } /** * Gets the scope where this variable is declared. */ Scope getScope() { return scope; } /** * Returns the index within the scope stack. * e.g. function Foo(a) { var b; function c(d) { } } * a = 0, b = 1, c = 2, d = 3 */ int getLocalVarIndex() { int num = index; Scope s = scope.getParent(); if (s == null) { throw new IllegalArgumentException("Var is not local"); } while (s.getParent() != null) { num += s.getVarCount(); s = s.getParent(); } return num; } /** * Returns whether this is a global variable. */ public boolean isGlobal() { return scope.isGlobal(); } /** * Returns whether this is a local variable. */ public boolean isLocal() { return scope.isLocal(); } /** * Returns whether this is defined in an extern file. */ boolean isExtern() { return input == null || input.isExtern(); } /** * Returns {@code true} if the variable is declared as a constant, * based on the value reported by {@code NodeUtil}. */ public boolean isConst() { return NodeUtil.isConstantName(nameNode); } /** * Returns {@code true} if the variable is declared as a define. * A variable is a define if it is annotaed by {@code @define}. */ public boolean isDefine() { return isDefine; } public Node getInitialValue() { Node parent = getParentNode(); int pType = parent.getType(); if (pType == Token.FUNCTION) { return parent; } else if (p

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Type == Token.ASSIGN) { return parent.getLastChild(); } else if (pType == Token.VAR) { return nameNode.getFirstChild(); } else { return null; } } /** * Gets this variable's type. To know whether this type has been inferred, * see {@code #isInferred()}. */ public JSType getType() { return type; } /** * Returns the name node that produced this variable. */ public Node getNameNode() { return nameNode; } /** * Gets the JSDocInfo for the variable. */ public JSDocInfo getJSDocInfo() { return info; } /** * Sets this variable's type. * @throws IllegalStateException if the variable's type is not inferred */ void setType(JSType type) { Preconditions.checkState(isTypeInferred()); this.type = type; } /** * Resolve this variable's type. */ void resolveType(ErrorReporter errorReporter) { if (type != null) { type = type.resolve(errorReporter, scope); } } /** * Returns whether this variable's type is inferred. To get the variable's * type, see {@link #getType()}. */ public boolean isTypeInferred() { return typeInferred; } public String getInputName() { if (input == null) return "<non-file>"; else return input.getName(); } public boolean isNoShadow() { if (info != null && info.isNoShadow()) { return true; } else { return false; } } @Override public boolean equals(Object other) { if (!(other instanceof Var)) { return false; } Var otherVar = (Var) other; return otherVar.nameNode == nameNode; } @Override public int hashCode() { return nameNode.hashCode(); } @Override public String toString() { return "Scope.Var " + name; } } /** * Creates a Scope given the parent Scope and the root node of the scope. * @param parent The parent Scope. Cannot be null. * @param rootNode Typically the FUNCTION node. */ Scope(Scope parent, Node rootNode) { Preconditions.checkNotNull(parent); Preconditions.checkArgument(rootNode != parent.rootNode); this.parent = parent; this.rootNode = rootNode; JSType nodeType = rootNode.getJSType(); if (nodeType != null && nodeType instanceof FunctionType) { thisType = ((FunctionType) nodeType).getTypeOfThis(); } else { thisType = parent.thisType; } this.isBottom = false; } /** * Creates a global Scope. * @param rootNode Typically the global BLOCK node. */ Scope(Node rootNode, AbstractCompiler compiler) { this.parent = null; this.root

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Node = rootNode; thisType = compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS); this.isBottom = false; } /** * Creates a empty Scope (bottom of the lattice). * @param rootNode Typically a FUNCTION node or the global BLOCK node. * @param thisType the type of {@code this} in this scope */ Scope(Node rootNode, ObjectType thisType) { this.parent = null; this.rootNode = rootNode; this.thisType = thisType; this.isBottom = true; } /** Whether this is the bottom of the lattice. */ boolean isBottom() { return isBottom; } /** * Gets the container node of the scope. This is typically the FUNCTION * node or the global BLOCK/SCRIPT node. */ public Node getRootNode() { return rootNode; } public Scope getParent() { return parent; } Scope getGlobalScope() { Scope result = this; while (result.getParent() != null) { result = result.getParent(); } return result; } @Override public StaticScope<JSType> getParentScope() { return parent; } /** * Gets the type of {@code this} in the current scope. */ public ObjectType getTypeOfThis() { return thisType; } /** * Declares a variable whose type is inferred. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. */ Var declare(String name, Node nameNode, JSType type, CompilerInput input) { return declare(name, nameNode, type, input, true); } /** * Declares a variable. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. * @param inferred Whether this variable's type is inferred (as opposed * to declared). */ Var declare(String name, Node nameNode, JSType type, CompilerInput input, boolean inferred) { Preconditions.checkState(name != null && name.length() > 0); // Make sure that it's declared only once Preconditions.checkState(vars.get(name) == null); Var var = new Var(inferred); var.name = name; var.nameNode = nameNode; var.type = type; var.scope = this; var.index = vars.size(); var.input = input; // native variables do not have a name node. // TODO(user): make Var abstract and have NativeVar, NormalVar. JSDocInfo info = NodeUtil.getInfoForNameNode(nameNode); var.isDefine = info != null && info.isDefine(); var.info

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> = info; vars.put(name, var); return var; } /** * Undeclares a variable, to be used when the compiler optimizes out * a variable and removes it from the scope. */ void undeclare(Var var) { Preconditions.checkState(var.scope == this); Preconditions.checkState(vars.get(var.name) == var); vars.remove(var.name); } public StaticSlot<JSType> getSlot(String name) { return getVar(name); } public StaticSlot<JSType> getOwnSlot(String name) { return vars.get(name); } /** * Returns the variable, may be null */ public Var getVar(String name) { Var var = vars.get(name); if (var != null) { return var; } else if (parent != null) { // Recurse up the parent Scope return parent.getVar(name); } else { return null; } } /** * Returns true if a variable is declared. */ public boolean isDeclared(String name, boolean recurse) { Scope scope = this; if (scope.vars.containsKey(name)) return true; if (scope.parent != null && recurse) { return scope.parent.isDeclared(name, recurse); } return false; } /** * Return an iterator over all of the variables declared in this scope. */ public Iterator<Var> getVars() { return vars.values().iterator(); } /** * Returns number of variables in this scope */ public int getVarCount() { return vars.size(); } /** * Returns whether this is the global scope. */ public boolean isGlobal() { return parent == null; } /** * Returns whether this is a local scope (i.e. not the global scope). */ public boolean isLocal() { return !isGlobal(); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, entering from the given node. * @param graph The graph to traverse. * @param entry The node to begin traversing from. */ public void computeFixedPoint(DiGraph<N, E> graph, N entry) { Set<N> entrySet = Sets.newHashSet(); entrySet.add(entry); computeFixedPoint(graph, entrySet); } /** * Compute a fixed point for the given graph, entering from the given nodes. * @param graph The graph to traverse. * @param entrySet The nodes to begin traversing from. */ public void computeFixedPoint(DiGraph<N, E> graph, Set<N> entrySet) { int cycleCount = 0; long nodeCount = graph.getNodes().size(); // Choose a bail-out heuristically in case the computation // doesn't converge. long maxIterations = Math.max(nodeCount * nodeCount * nodeCount, 100); // Use a LinkedHashSet, so that the traversal is deterministic. LinkedHashSet<DiGraphNode<N, E>> workSet = Sets.newLinkedHashSet(); for (N n : entrySet) { workSet.add(graph.getDirectedGraphNode(n)); } for (; !workSet.isEmpty() && cycleCount < maxIterations; cycleCount++) { // For every out edge in the workSet, traverse that edge. If that // edge updates the state of the graph, then add the destination // node to the resultSet, so that we can update all of its out edges // on the next iteration. DiGraphNode<N, E> source = workSet.iterator().next(); N sourceValue = source.getValue(); workSet.remove(source); List<DiGraphEdge<N, E>> outEdges = source.getOutEdges(); for (DiGraphEdge<N, E> edge : outEdges) { N destNode = edge.getDestination().getValue(); if (callback.traverseEdge(sourceValue, edge.getValue(), destNode)) { workSet.add(edge.getDestination()); } } } Preconditions.checkState(cycleCount != maxIterations, NON_HALTING_ERROR_MSG); } public static interface EdgeCallback<Node, Edge> { /** * Update the state of the destination node when the given edge * is traversed. For the fixed-point computation to work, only the * destination node may be modified. The source node and the edge must * not be modified. * * @param source The start node. * @param e The edge. * @param destination The end node. * @return Whether the state of the destination node changed. */ boolean traverseEdge(Node source, Edge e, Node destination); } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Checks for misplaced semicolons, such as * <pre> * if (foo()); act_now(); * </pre> * and generates warnings. * */ final class CheckAccidentalSemicolon extends AbstractPostOrderCallback { static final DiagnosticType SUSPICIOUS_SEMICOLON = DiagnosticType.warning( "JSC_SUSPICIOUS_SEMICOLON", "If this if/for/while really shouldn't have a body, use {}"); private final CheckLevel level; CheckAccidentalSemicolon(CheckLevel level) { this.level = level; } public void visit(NodeTraversal t, Node n, Node parent) { Node child; switch (n.getType()) { case Token.IF: child = n.getFirstChild().getNext(); // skip the condition child break; case Token.WHILE: case Token.FOR: child = NodeUtil.getLoopCodeBlock(n); break; default: return; // don't check other types } // semicolons cause VOID children. Empty blocks are allowed because // that's usually intentional, especially with loops. for (; child != null; child = child.getNext()) { if ((child.getType() == Token.BLOCK) && (!child.hasChildren())) { // Only warn on empty blocks that replaced EMPTY nodes. BLOCKs with no // children are considered OK. if (child.wasEmptyNode()) { t.getCompiler().report( t.makeError(n, level, SUSPICIOUS_SEMICOLON)); } } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.Callback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * A simple pass to ensure that all AST nodes have line numbers, * an that the line numbers are monotonically increasing. * * @author nicksantos@google.com (Nick Santos) */ class LineNumberCheck implements Callback, CompilerPass { static final DiagnosticType MISSING_LINE_INFO = DiagnosticType.error( "JSC_MISSING_LINE_INFO", "No source location information associated with {0}.\n" + "Most likely a Node has been created with settings the source file " + "and line/column location. Usually this is done using " + "Node.copyInformationFrom and supplying a Node from the source AST."); private final AbstractCompiler compiler; private boolean requiresLineNumbers = false; LineNumberCheck(AbstractCompiler compiler) { this.compiler = compiler; } public void setCheckSubTree(Node root) { requiresLineNumbers = true; NodeTraversal.traverse(compiler, root, this); } public void process(Node externs, Node root) { requiresLineNumbers = false; NodeTraversal.traverse(compiler, root, this); } public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { // Each JavaScript file is rooted in a script node, so we'll only // have line number information inside the script node. if (n.getType() == Token.SCRIPT) { requiresLineNumbers = true; } return true; } public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.SCRIPT) { requiresLineNumbers = false; } else if (requiresLineNumbers) { if (n.getLineno() == -1) { // The tree version of the node is really the best diagnostic // info we have to offer here. compiler.report( t.makeError(n, MISSING_LINE_INFO, n.toStringTree())); } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Node errorRoot, String sourceName, Scope scope) { Preconditions.checkNotNull(errorRoot); this.fnName = fnName == null ? "" : fnName; this.codingConvention = compiler.getCodingConvention(); this.typeRegistry = compiler.getTypeRegistry(); this.errorRoot = errorRoot; this.sourceName = sourceName; this.compiler = compiler; this.scope = scope; } /** * Sets the FUNCTION node of this function. */ FunctionTypeBuilder setSourceNode(@Nullable Node sourceNode) { this.sourceNode = sourceNode; return this; } /** * Infer the parameter and return types of a function from * the parameter and return types of the function it is overriding. * * @param oldType The function being overridden. Does nothing if this is null. * @param paramsParent The LP node of the function that we're assigning to. * If null, that just means we're not initializing this to a function * literal. */ FunctionTypeBuilder inferFromOverriddenFunction( @Nullable FunctionType oldType, @Nullable Node paramsParent) { if (oldType == null) { return this; } returnType = oldType.getReturnType(); returnTypeInferred = oldType.isReturnTypeInferred(); if (paramsParent == null) { // Not a function literal. parametersNode = oldType.getParametersNode(); if (parametersNode == null) { parametersNode = new FunctionParamBuilder(typeRegistry).build(); } } else { // We're overriding with a function literal. Apply type information // to each parameter of the literal. FunctionParamBuilder paramBuilder = new FunctionParamBuilder(typeRegistry); Iterator<Node> oldParams = oldType.getParameters().iterator(); boolean warnedAboutArgList = false; boolean oldParamsListHitOptArgs = false; for (Node currentParam = paramsParent.getFirstChild(); currentParam != null; currentParam = currentParam.getNext()) { if (oldParams.hasNext()) { Node oldParam = oldParams.next(); Node newParam = paramBuilder.newParameterFromNode(oldParam); oldParamsListHitOptArgs = oldParamsListHitOptArgs || oldParam.isVarArgs() || oldParam.isOptionalArg(); // The subclass method might right its var_args as individual // arguments. if (currentParam.getNext() != null && newParam.isVarArgs()) { newParam.setVarArgs(false); newParam.setOptionalArg(true); } } else { warnedAboutArgList |= addParameter( paramBuilder, typeRegistry.getNativeType(UNKNOWN_TYPE), warnedAboutArgList, codingConvention.isOptionalParameter(currentParam) || oldParamsListHitOptArgs, codingConvention.isVarArgsParameter(currentParam)); } } parametersNode = paramBuilder.build(); } return this; } /** * Infer the

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return type from JSDocInfo. */ FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) { if (info != null && info.hasReturnType()) { returnType = info.getReturnType().evaluate(scope, typeRegistry); returnTypeInferred = false; } if (templateTypeName != null && returnType != null && returnType.restrictByNotNullOrUndefined().isTemplateType()) { reportError(TEMPLATE_TYPE_EXPECTED, fnName); } return this; } /** * If we haven't found a return value yet, try to look at the "return" * statements in the function. */ FunctionTypeBuilder inferReturnStatementsAsLastResort( @Nullable Node functionBlock) { if (functionBlock == null || compiler.getInput(sourceName).isExtern()) { return this; } Preconditions.checkArgument(functionBlock.getType() == Token.BLOCK); if (returnType == null) { boolean hasNonEmptyReturns = false; List<Node> worklist = Lists.newArrayList(functionBlock); while (!worklist.isEmpty()) { Node current = worklist.remove(worklist.size() - 1); int cType = current.getType(); if (cType == Token.RETURN && current.getFirstChild() != null || cType == Token.THROW) { hasNonEmptyReturns = true; break; } else if (NodeUtil.isStatementBlock(current) || NodeUtil.isControlStructure(current)) { for (Node child = current.getFirstChild(); child != null; child = child.getNext()) { worklist.add(child); } } } if (!hasNonEmptyReturns) { returnType = typeRegistry.getNativeType(VOID_TYPE); returnTypeInferred = true; } } return this; } /** * Infer the role of the function (whether it's a constructor or interface) * and what it inherits from in JSDocInfo. */ FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) { if (info != null) { isConstructor = info.isConstructor(); isInterface = info.isInterface(); // base type if (info.hasBaseType()) { if (isConstructor || isInterface) { JSType maybeBaseType = info.getBaseType().evaluate(scope, typeRegistry); if (maybeBaseType != null && maybeBaseType.setValidator(new ExtendedTypeValidator())) { baseType = (ObjectType) maybeBaseType; } } else { reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName); } } // implemented interfaces if (isConstructor || isInterface) { implementedInterfaces = Lists.newArrayList(); for (JSTypeExpression t : info.getImplementedInterfaces()) { JSType maybeInterType = t.evaluate(scope, typeRegistry); if (maybeInterType != null && maybeInterType.setValidator(new ImplementedTypeValidator())) { implementedInterfaces.add((

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ObjectType) maybeInterType); } } if (baseType != null) { JSType maybeFunctionType = baseType.getConstructor(); if (maybeFunctionType instanceof FunctionType) { FunctionType functionType = baseType.getConstructor(); Iterables.addAll( implementedInterfaces, functionType.getImplementedInterfaces()); } } } else if (info.getImplementedInterfaceCount() > 0) { reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName); } } return this; } /** * Infers the type of {@code this}. * @param type The type of this. */ FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) { ObjectType objType = ObjectType.cast(type); if (objType != null && (info == null || !info.hasType())) { thisType = objType; } return this; } /** * Infers the type of {@code this}. * @param info The JSDocInfo for this function. * @param owner The node for the object whose prototype "owns" this function. * For example, {@code A} in the expression {@code A.prototype.foo}. May * be null to indicate that this is not a prototype property. */ FunctionTypeBuilder inferThisType(JSDocInfo info, @Nullable Node owner) { ObjectType maybeThisType = null; if (info != null && info.hasThisType()) { maybeThisType = ObjectType.cast( info.getThisType().evaluate(scope, typeRegistry)); } if (maybeThisType != null) { thisType = maybeThisType; thisType.setValidator(new ThisTypeValidator()); } else if (owner != null && (info == null || !info.hasType())) { // If the function is of the form: // x.prototype.y = function() {} // then we can assume "x" is the @this type. On the other hand, // if it's of the form: // /** @type {Function} */ x.prototype.y; // then we should not give it a @this type. String ownerTypeName = owner.getQualifiedName(); ObjectType ownerType = ObjectType.cast( typeRegistry.getForgivingType( scope, ownerTypeName, sourceName, owner.getLineno(), owner.getCharno())); if (ownerType != null) { thisType = ownerType; } } return this; } /** * Infer the parameter types from the doc info alone. */ FunctionTypeBuilder inferParameterTypes(JSDocInfo info) { // Create a fake args parent. Node lp = new Node(Token.LP); for (String name : info.getParameterNames()) { lp.addChildToBack(Node.newString(Token.NAME, name)); } return inferParameterTypes(lp, info); } /** * Infer the parameter

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) { typeRegistry.declareType(fnName, fnType.getInstanceType()); } maybeSetBaseType(fnType); } else { fnType = new FunctionBuilder(typeRegistry) .withName(fnName) .withSourceNode(sourceNode) .withParamsNode(parametersNode) .withReturnType(returnType, returnTypeInferred) .withTypeOfThis(thisType) .withTemplateName(templateTypeName) .build(); maybeSetBaseType(fnType); } if (implementedInterfaces != null) { fnType.setImplementedInterfaces(implementedInterfaces); } typeRegistry.clearTemplateTypeName(); return fnType; } private void maybeSetBaseType(FunctionType fnType) { if (baseType != null) { fnType.setPrototypeBasedOn(baseType); } } /** * Returns a constructor function either by returning it from the * registry if it exists or creating and registering a new type. If * there is already a type, then warn if the existing type is * different than the one we are creating, though still return the * existing function if possible. The primary purpose of this is * that registering a constructor will fail for all built-in types * that are initialized in {@link JSTypeRegistry}. We a) want to * make sure that the type information specified in the externs file * matches what is in the registry and b) annotate the externs with * the {@link JSType} from the registry so that there are not two * separate JSType objects for one type. */ private FunctionType getOrCreateConstructor() { FunctionType fnType = typeRegistry.createConstructorType( fnName, sourceNode, parametersNode, returnType); JSType existingType = typeRegistry.getType(fnName); if (existingType != null) { boolean isInstanceObject = existingType instanceof InstanceObjectType; if (isInstanceObject || fnName.equals("Function")) { FunctionType existingFn = isInstanceObject ? ((InstanceObjectType) existingType).getConstructor() : typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE); if (existingFn.getSource() == null) { existingFn.setSource(sourceNode); } if (!existingFn.hasEqualCallType(fnType)) { reportWarning(TYPE_REDEFINITION, fnName, fnType.toString(), existingFn.toString()); } return existingFn; } else { // We fall through and return the created type, even though it will fail // to register. We have no choice as we have to return a function. We // issue an error elsewhere though, so the user should fix it. } } maybeSetBaseType(fnType); if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) { typeRegistry.declareType(fnName, fnType.getInstanceType()); } return fnType; } private void reportWarning

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> to descendant names should emit warnings. */ private void checkDescendantNames(Name name, boolean nameIsDefined) { if (name.props != null) { for (Name prop : name.props) { // if the ancestor of a property is not defined, then we should emit // warnings for all references to the property. boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.declaration; if (!isDefined) { if (declaration != null) { reportRefToUndefinedName(name, declaration); } } if (name.refs != null) { JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.refs) { if (!isDefined) { reportRefToUndefinedName(name, ref); } else { if (declaration != null && ref.module != declaration.module && !moduleGraph.dependsOn(ref.module, declaration.module)) { reportBadModuleReference(name, ref); } } } } } private void reportBadModuleReference(Name name, Ref ref) { compiler.report( JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME, ref.module.getName(), name.declaration.module.getName(), name.fullName())); } private void reportRefToUndefinedName(Name name, Ref ref) { // grab the highest undefined ancestor to output in the warning message. while (name.parent != null && name.parent.globalSets + name.parent.localSets == 0) { name = name.parent; } // If this is an annotated EXPR-GET, don't do anything. Node parent = ref.node.getParent(); if (parent.getType() == Token.EXPR_RESULT) { JSDocInfo info = ref.node.getJSDocInfo(); if (info != null && info.hasTypedefType()) { return; } } compiler.report( JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING, name.fullName())); } /** * Checks whether the given name is a property, and whether that property * must be initialized with its full qualified name. */ private static boolean propertyMustBeInitializedByFullName(Name name) { // If an object literal

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * * public static void main(String[] args) { * (new MyCommandLineRunner(args)).run(); * } * } * </pre> * * @author bolinfest@google.com (Michael Bolin) */ abstract class AbstractCommandLineRunner<A extends Compiler, B extends CompilerOptions> { private final CommandLineConfig config; private Appendable out; private final PrintStream err; private A compiler; private Charset inputCharset; private String outputCharset; private boolean testMode = false; private Supplier<List<JSSourceFile>> externsSupplierForTesting = null; private Supplier<List<JSSourceFile>> inputsSupplierForTesting = null; private Supplier<List<JSModule>> modulesSupplierForTesting = null; private Function<Integer, Boolean> exitCodeReceiverForTesting = null; // Bookkeeping to measure optimal phase orderings. private static final int NUM_RUNS_TO_DETERMINE_OPTIMAL_ORDER = 100; private final RunTimeStats runTimeStats = new RunTimeStats(); AbstractCommandLineRunner() { this(System.out, System.err); } AbstractCommandLineRunner(PrintStream out, PrintStream err) { this.config = new CommandLineConfig(); this.out = out; this.err = err; } /** * Put the command line runner into test mode. In test mode, * all outputs will be blackholed. * @param externsSupplier A provider for externs. * @param inputsSupplier A provider for source inputs. * @param modulesSupplier A provider for modules. Only one of inputsSupplier * and modulesSupplier may be non-null. * @param exitCodeReceiver A receiver for the status code that would * have been passed to System.exit in non-test mode. */ @VisibleForTesting void enableTestMode( Supplier<List<JSSourceFile>> externsSupplier, Supplier<List<JSSourceFile>> inputsSupplier, Supplier<List<JSModule>> modulesSupplier, Function<Integer, Boolean> exitCodeReceiver) { Preconditions.checkArgument( inputsSupplier == null ^ modulesSupplier == null); testMode = true; this.externsSupplierForTesting = externsSupplier; this.inputsSupplierForTesting = inputsSupplier; this.modulesSupplierForTesting = modulesSupplier; this.exitCodeReceiverForTesting = exitCodeReceiver; } /** * Returns whether we're in test mode. */ protected boolean isInTestMode() { return testMode; } /** * Get the command line config, so that it can be initialized. */ protected CommandLineConfig getCommandLineConfig() { return config; } /** * Returns the instance of the Compiler to use when {@link #run()} is * called. */ protected abstract A createCompiler(); /** * Returns the instance of the Options to use when {@link #run()} is called. * createCompiler() is called before createOptions(),

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>fromCode("/dev/null", "")); } try { return createInputs(files, false); } catch (FlagUsageException e) { throw new FlagUsageException("Bad --externs flag. " + e.getMessage()); } } /** * Creates module objects from a list of module specifications. * * @param specs A list of module specifications, not null or empty. The spec * format is: <code>name:num-js-files[:[dep,...][:]]</code>. Module * names must not contain the ':' character. * @param jsFiles A list of js file paths, not null * @return An array of module objects */ List<JSModule> createJsModules( List<String> specs, List<String> jsFiles) throws FlagUsageException, IOException { if (isInTestMode()) { return modulesSupplierForTesting.get(); } Preconditions.checkState(specs != null); Preconditions.checkState(!specs.isEmpty()); Preconditions.checkState(jsFiles != null); final int totalNumJsFiles = jsFiles.size(); int nextJsFileIndex = 0; Map<String, JSModule> modulesByName = Maps.newLinkedHashMap(); for (String spec : specs) { // Format is "<name>:<num-js-files>[:[<dep>,...][:]]". String[] parts = spec.split(":"); if (parts.length < 2 || parts.length > 4) { throw new FlagUsageException("Expected 2-4 colon-delimited parts in " + "module spec: " + spec); } // Parse module name. String name = parts[0]; if (!TokenStream.isJSIdentifier(name)) { throw new FlagUsageException("Invalid module name: '" + name + "'"); } if (modulesByName.containsKey(name)) { throw new FlagUsageException("Duplicate module name: " + name); } JSModule module = new JSModule(name); // Parse module inputs. int numJsFiles = -1; try { numJsFiles = Integer.parseInt(parts[1]); } catch (NumberFormatException ignored) { numJsFiles = -1; } // We will allow modules of zero input. if (numJsFiles < 0) { throw new FlagUsageException("Invalid js file count '" + parts[1] + "' for module: " + name); } if (nextJsFileIndex + numJsFiles > totalNumJsFiles) { throw new FlagUsageException("Not enough js files specified. Expected " + (nextJsFileIndex + numJsFiles - totalNumJsFiles) + " more in module:" + name); } List<String> moduleJsFiles = jsFiles.subList(nextJsFileIndex, nextJsFileIndex + numJsFiles); for (JSSourceFile input : createInputs(moduleJsFiles, false)) { module.add(input); } nextJs

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>FileIndex += numJsFiles; if (parts.length > 2) { // Parse module dependencies. String depList = parts[2]; if (depList.length() > 0) { String[] deps = depList.split(","); for (String dep : deps) { JSModule other = modulesByName.get(dep); if (other == null) { throw new FlagUsageException("Module '" + name + "' depends on unknown module '" + dep + "'. Be sure to list modules in dependency order."); } module.addDependency(other); } } } modulesByName.put(name, module); } if (nextJsFileIndex < totalNumJsFiles) { throw new FlagUsageException("Too many js files specified. Expected " + nextJsFileIndex + " but found " + totalNumJsFiles); } return Lists.newArrayList(modulesByName.values()); } /** * Parses module wrapper specifications. * * @param specs A list of module wrapper specifications, not null. The spec * format is: <code>name:wrapper</code>. Wrappers. * @param modules The JS modules whose wrappers are specified * @return A map from module name to module wrapper. Modules with no wrapper * will have the empty string as their value in this map. */ static Map<String, String> parseModuleWrappers(List<String> specs, List<JSModule> modules) throws FlagUsageException { Preconditions.checkState(specs != null); Map<String, String> wrappers = Maps.newHashMapWithExpectedSize(modules.size()); // Prepopulate the map with module names. for (JSModule m : modules) { wrappers.put(m.getName(), ""); } for (String spec : specs) { // Format is "<name>:<wrapper>". int pos = spec.indexOf(':'); if (pos == -1) { throw new FlagUsageException("Expected module wrapper to have " + "<name>:<wrapper> format: " + spec); } // Parse module name. String name = spec.substring(0, pos); if (!wrappers.containsKey(name)) { throw new FlagUsageException("Unknown module: '" + name + "'"); } String wrapper = spec.substring(pos + 1); if (!wrapper.contains("%s")) { throw new FlagUsageException("No %s placeholder in module wrapper: '" + wrapper + "'"); } wrappers.put(name, wrapper); } return wrappers; } /** * Writes code to an output stream, optionally wrapping it in an arbitrary * wrapper that contains a placeholder where the code should be inserted. */ static void writeOutput(Appendable out, Compiler compiler, String code, String wrapper, String codePlaceholder) throws IOException { int pos = wrapper.indexOf(codePlaceholder); if (pos != -1) { String prefix = ""; if (pos > 0) { prefix = wrapper.substring(0

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> this.sourceMapFormat = format; return this; } private final List<String> jscompError = Lists.newArrayList(); /** * Make the named class of warnings an error. */ CommandLineConfig setJscompError(List<String> jscompError) { this.jscompError.clear(); this.jscompError.addAll(jscompError); return this; } private final List<String> jscompWarning = Lists.newArrayList(); /** * Make the named class of warnings a normal warning. */ CommandLineConfig setJscompWarning(List<String> jscompWarning) { this.jscompWarning.clear(); this.jscompWarning.addAll(jscompWarning); return this; } private final List<String> jscompOff = Lists.newArrayList(); /** * Turn off the named class of warnings. */ CommandLineConfig setJscompOff(List<String> jscompOff) { this.jscompOff.clear(); this.jscompOff.addAll(jscompOff); return this; } private final List<String> define = Lists.newArrayList(); /** * Override the value of a variable annotated @define. * The format is <name>[=<val>], where <name> is the name of a @define * variable and <val> is a boolean, number, or a single-quoted string * that contains no single quotes. If [=<val>] is omitted, * the variable is marked true */ CommandLineConfig setDefine(List<String> define) { this.define.clear(); this.define.addAll(define); return this; } private String charset = ""; /** * Input charset for all files. */ CommandLineConfig setCharset(String charset) { this.charset = charset; return this; } private boolean manageClosureDependencies = false; /** * Sets whether to sort files by their goog.provide/require deps, * and prune inputs that are not required. */ CommandLineConfig setManageClosureDependencies(boolean newVal) { this.manageClosureDependencies = newVal; return this; } private List<String> closureEntryPoints = ImmutableList.of(); /** * Set closure entry points, which makes the compiler only include * those files and sort them in dependency order. */ CommandLineConfig setClosureEntryPoints(List<String> entryPoints) { Preconditions.checkNotNull(entryPoints); this.closureEntryPoints = entryPoints; return this; } private String outputManifest = ""; /** * Sets whether to print an output manifest file. */ CommandLineConfig setOutputManifest(String outputManifest) { this.outputManifest = outputManifest; return this; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.error("JSC_GETCSSNAME_UNEXPECTED_STRING_LITERAL", "goog.getCssName called with invalid arguments, string literal " + "passed as first of two arguments. Did you mean " + "goog.getCssName(\"{0}-{1}\")?"); static final DiagnosticType UNKNOWN_SYMBOL_WARNING = DiagnosticType.warning("JSC_GETCSSNAME_UNKNOWN_CSS_SYMBOL", "goog.getCssName called with unrecognized symbol \"{0}\" in class " + "\"{1}\"."); private final AbstractCompiler compiler; private final Map<String, Integer> cssNames; private CssRenamingMap symbolMap; private final JSType nativeStringType; ReplaceCssNames(AbstractCompiler compiler, @Nullable Map<String, Integer> cssNames) { this.compiler = compiler; this.cssNames = cssNames; this.nativeStringType = compiler.getTypeRegistry() .getNativeType(STRING_TYPE); } @Override public void process(Node externs, Node root) { // The CssRenamingMap may not have been available from the compiler when // this ReplaceCssNames pass was constructed, so getCssRenamingMap() should // only be called before this pass is actually run. symbolMap = getCssRenamingMap(); NodeTraversal.traverse(compiler, root, new Traversal()); } @VisibleForTesting protected CssRenamingMap getCssRenamingMap() { return compiler.getCssRenamingMap(); } private class Traversal extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.CALL && GET_CSS_NAME_FUNCTION.equals(n.getFirstChild().getQualifiedName())) { int count = n.getChildCount(); Node first = n.getFirstChild().getNext(); switch (count) { case 2: // Replace the function call with the processed argument. if (first.getType() == Token.STRING) { processStringNode(t, first); n.removeChild(first); parent.replaceChild(n, first); compiler.reportCodeChange(); } else { compiler.report(t.makeError(n, STRING_LITERAL_EXPECTED_ERROR, Token.name(first.getType()))); } break; case 3: // Replace function call with concatenation of two args. It's // assumed the first arg has already been processed. Node second = first.getNext(); if (first.getType() == Token.STRING) { compiler.report(t.makeError( n, UNEXPECTED_STRING_LITERAL_ERROR, first.getString(), second.getString())); } else if (second.getType() == Token.STRING) { processStringNode(t, second); n.removeChild(first); Node replacement = new Node(Token.ADD, first, Node.newString("-" + second.getString()) .copyInformationFrom(second))

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> .copyInformationFrom(n); replacement.setJSType(nativeStringType); parent.replaceChild(n, replacement); compiler.reportCodeChange(); } else { compiler.report(t.makeError(n, STRING_LITERAL_EXPECTED_ERROR, Token.name(second.getType()))); } break; default: compiler.report(t.makeError( n, INVALID_NUM_ARGUMENTS_ERROR, String.valueOf(count))); } } } /** * Processes a string argument to goog.getCssName(). The string will be * renamed based off the symbol map. If there is no map or any part of the * name can't be renamed, a warning is reported to the compiler and the node * is left unchanged. * * If the type is unexpected then an error is reported to the compiler. * * @param t The node traversal. * @param n The string node to process. */ private void processStringNode(NodeTraversal t, Node n) { if (symbolMap != null || cssNames != null) { String[] parts = n.getString().split("-"); for (int i = 0; i < parts.length; i++) { if (cssNames != null) { Integer count = cssNames.get(parts[i]); if (count == null) { count = Integer.valueOf(0); } cssNames.put(parts[i], count.intValue() + 1); } if (symbolMap != null) { String replacement = symbolMap.get(parts[i]); if (replacement == null) { // If we can't encode all parts, don't encode any of it. compiler.report(t.makeError( n, UNKNOWN_SYMBOL_WARNING, parts[i], n.getString())); return; } parts[i] = replacement; } } if (symbolMap != null) { n.setString(Joiner.on("-").join(parts)); } } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>$inherits(SubClass, SuperClass) // SubClass.mixin(SuperClass.prototype) // goog.mixin(SubClass.prototype, SuperClass.prototype) // goog$mixin(SubClass.prototype, SuperClass.prototype) if (callNode.getChildCount() == 2 && callName.getType() == Token.GETPROP) { // SubClass.inherits(SuperClass) subclass = callName.getFirstChild(); } else if (callNode.getChildCount() == 3) { // goog.inherits(SubClass, SuperClass) subclass = callName.getNext(); } // bail out if either of the side of the "inherits" // isn't a real class name. This prevents us from // doing something weird in cases like: // goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2) if (subclass != null && subclass.isUnscopedQualifiedName() && superclass.isUnscopedQualifiedName()) { // make sure to strip the prototype off of the nodes // to normalize for goog.mixin return new SubclassRelationship( type, stripPrototype(subclass), stripPrototype(superclass)); } } return null; } /** * Determines whether the given node is a class-defining name, like * "inherits" or "mixin." * @return The type of class-defining name, or null. */ private SubclassType typeofClassDefiningName(Node callName) { // Check if the method name matches one of the class-defining methods. String methodName = null; if (callName.getType() == Token.GETPROP) { methodName = callName.getLastChild().getString(); } else if (callName.getType() == Token.NAME) { String name = callName.getString(); int dollarIndex = name.lastIndexOf('$'); if (dollarIndex != -1) { methodName = name.substring(dollarIndex + 1); } } if (methodName != null) { if (methodName.equals("inherits")) { return SubclassType.INHERITS; } else if (methodName.equals("mixin")) { return SubclassType.MIXIN; } } return null; } @Override public boolean isSuperClassReference(String propertyName) { return "superClass_".equals(propertyName); } /** * Given a qualified name node, strip "prototype" off the end. * * Examples of this transformation: * a.b.c => a.b.c * a.b.c.prototype => a.b.c */ private Node stripPrototype(Node qualifiedName) { if (qualifiedName.getType() == Token.GETPROP && qualifiedName.getLastChild().getString().equals("prototype")) { return qualifiedName.getFirstChild(); } return qualifiedName; } /** * Exctracts X from goog.provide('X'), if the applied Node is goog. * *

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> @return The extracted class name, or null. */ @Override public String extractClassNameIfProvide(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.provide"); } /** * Exctracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.getType() == Token.GETPROP) { String qualifiedName = callee.getQualifiedName(); if ((functionName).equals(qualifiedName)) { className = callee.getNext().getString(); } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.getType() == Token.ARRAYLIT) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.getType() == Token.STRING) { typeNames.add(name.getString()); } } return typeNames; } } return null; } @Override public String identifyTypeDefAssign(Node n) { Node firstChild = n.getFirstChild(); int type = n.getType(); if (type == Token.ASSIGN) { if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) { return firstChild.getQualifiedName(); } } else if (type == Token.VAR && firstChild.hasChildren()) { if (TYPEDEF_NAME.equals( firstChild.getFirstChild().getQualifiedName())) { return firstChild.getString(); } } return null; } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); String callName = callArg.get

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>QualifiedName(); // Use both the original name and the post-CollapseProperties name. if (!("goog.addSingletonGetter".equals(callName) || "goog$addSingletonGetter".equals(callName)) || callNode.getChildCount() != 2) { return null; } return callArg.getNext().getQualifiedName(); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { functionType.defineDeclaredProperty("getInstance", getterType, false); functionType.defineDeclaredProperty("instance_", objectType, false); } @Override public String getGlobalObject() { return "goog.global"; } private final Set<String> propertyTestFunctions = ImmutableSet.of( "goog.isDef", "goog.isNull", "goog.isDefAndNotNull", "goog.isString", "goog.isNumber", "goog.isBoolean", "goog.isFunction", "goog.isArray", "goog.isObject"); @Override public boolean isPropertyTestFunction(Node call) { Preconditions.checkArgument(call.getType() == Token.CALL); return propertyTestFunctions.contains( call.getFirstChild().getQualifiedName()); } @Override public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t, Node callNode) { Preconditions.checkArgument(callNode.getType() == Token.CALL); Node callName = callNode.getFirstChild(); if (!"goog.reflect.object".equals(callName.getQualifiedName()) || callName.getChildCount() != 2) { return null; } Node typeNode = callName.getNext(); if (!typeNode.isQualifiedName()) { return null; } Node objectNode = typeNode.getNext(); if (objectNode.getType() != Token.OBJECTLIT) { t.getCompiler().report(JSError.make(t.getSourceName(), callNode, OBJECTLIT_EXPECTED)); return null; } return new ObjectLiteralCast(typeNode.getQualifiedName(), typeNode.getNext()); } @Override public boolean isOptionalParameter(Node parameter) { return false; } @Override public boolean isVarArgsParameter(Node parameter) { return false; } @Override public boolean isPrivate(String name) { return false; } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return ImmutableList.<AssertionFunctionSpec>of( new AssertionFunctionSpec("goog.asserts.assert"), new AssertionFunctionSpec("goog.asserts.assertNumber", JSTypeNative.NUMBER_TYPE), new AssertionFunctionSpec("goog.asserts.assertString", JSTypeNative.STRING_TYPE), new AssertionFunctionSpec("goog.asserts.assertFunction", JSTypeNative.FUNCTION_INSTANCE_TYPE), new AssertionFunctionSpec("goog.asserts.assertObject", JSTypeNative.OBJECT_TYPE), new AssertionFunctionSpec("goog.asserts.assertArray

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case FIXUPS_PROP: return "fixups"; case VARS_PROP: return "vars"; case USES_PROP: return "uses"; case REGEXP_PROP: return "regexp"; case CASES_PROP: return "cases"; case DEFAULT_PROP: return "default"; case CASEARRAY_PROP: return "casearray"; case SOURCENAME_PROP: return "sourcename"; case TYPE_PROP: return "type"; case SPECIAL_PROP_PROP: return "special_prop"; case LABEL_PROP: return "label"; case FINALLY_PROP: return "finally"; case LOCALCOUNT_PROP: return "localcount"; case TARGETBLOCK_PROP: return "targetblock"; case VARIABLE_PROP: return "variable"; case LASTUSE_PROP: return "lastuse"; case ISNUMBER_PROP: return "isnumber"; case DIRECTCALL_PROP: return "directcall"; case SPECIALCALL_PROP: return "specialcall"; case DEBUGSOURCE_PROP: return "debugsource"; case JSDOC_INFO_PROP: return "jsdoc_info"; case SKIP_INDEXES_PROP: return "skip_indexes"; case INCRDECR_PROP: return "incrdecr"; case MEMBER_TYPE_PROP: return "member_type"; case NAME_PROP: return "name"; case PARENTHESIZED_PROP: return "parenthesized"; case QUOTED_PROP: return "quoted"; case SYNTHETIC_BLOCK_PROP: return "synthetic"; case EMPTY_BLOCK: return "empty_block"; case ORIGINALNAME_PROP: return "originalname"; case SIDE_EFFECT_FLAGS: return "side_effect_flags"; case IS_CONSTANT_NAME: return "is_constant_name"; case IS_OPTIONAL_PARAM: return "is_optional_param"; case IS_VAR_ARGS_PARAM: return "is_var_args_param"; case IS_NAMESPACE: return "is_namespace"; case IS_DISPATCHER: return "is_dispatcher"; case DIRECTIVES: return "directives"; case DIRECT_EVAL: return "direct_eval"; case FREE_CALL: return "free_call"; default: Kit.codeBug(); } return null; } private static class NumberNode extends Node { private static final long serialVersionUID = 1L; NumberNode(double number) { super(Token.NUMBER); this.number = number; } public NumberNode(double number, int lineno, int charno) { super(Token.NUMBER, lineno, charno); this.number = number; } @Override public double getDouble() { return this.number; } @Override public void setDouble(double d) { this.number = d

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; } @Override public boolean isEquivalentTo(Node node) { return (node instanceof NumberNode && getDouble() == ((NumberNode) node).getDouble()); } private double number; } private static class StringNode extends Node { private static final long serialVersionUID = 1L; StringNode(int type, String str) { super(type); if (null == str) { throw new IllegalArgumentException("StringNode: str is null"); } this.str = str; } StringNode(int type, String str, int lineno, int charno) { super(type, lineno, charno); if (null == str) { throw new IllegalArgumentException("StringNode: str is null"); } this.str = str; } /** * returns the string content. * @return non null. */ @Override public String getString() { return this.str; } /** * sets the string content. * @param str the new value. Non null. */ @Override public void setString(String str) { if (null == str) { throw new IllegalArgumentException("StringNode: str is null"); } this.str = str; } @Override public boolean isEquivalentTo(Node node) { return (node instanceof StringNode && this.str.equals(((StringNode) node).str)); } /** * If the property is not defined, this was not a quoted key. The * QUOTED_PROP int property is only assigned to STRING tokens used as * object lit keys. * @return true if this was a quoted string key in an object literal. */ @Override public boolean isQuotedString() { return getBooleanProp(QUOTED_PROP); } /** * This should only be called for STRING nodes created in object lits. */ @Override public void setQuotedString() { putBooleanProp(QUOTED_PROP, true); } private String str; } // PropListItems are immutable so that they can be shared. private static class PropListItem implements Serializable { private static final long serialVersionUID = 1L; final PropListItem next; final int type; final int intValue; final Object objectValue; PropListItem(int type, int intValue, PropListItem next) { this(type, intValue, null, next); } PropListItem(int type, Object objectValue, PropListItem next) { this(type, 0, objectValue, next); } PropListItem( int type, int intValue, Object objectValue, PropListItem next) { this.type = type; this.intValue = intValue; this.objectValue = objectValue; this.next = next; } } public Node(int nodeType) { type = nodeType; parent = null; sourcePosition = -1; } public Node(int nodeType, Node child) { Preconditions

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.checkArgument(child.parent == null, "new child has existing parent"); Preconditions.checkArgument(child.next == null, "new child has existing sibling"); type = nodeType; parent = null; first = last = child; child.next = null; child.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node right) { Preconditions.checkArgument(left.parent == null, "first new child has existing parent"); Preconditions.checkArgument(left.next == null, "first new child has existing sibling"); Preconditions.checkArgument(right.parent == null, "second new child has existing parent"); Preconditions.checkArgument(right.next == null, "second new child has existing sibling"); type = nodeType; parent = null; first = left; last = right; left.next = right; left.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent = this; mid.next = right; mid.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node mid2, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(mid2.parent == null); Preconditions.checkArgument(mid2.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent = this; mid.next = mid2; mid.parent = this; mid2.next = right; mid2.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, int lineno, int charno) { type = nodeType; parent = null; sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node child, int lineno, int charno) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> this(nodeType, child); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node right, int lineno, int charno) { this(nodeType, left, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node right, int lineno, int charno) { this(nodeType, left, mid, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node mid2, Node right, int lineno, int charno) { this(nodeType, left, mid, mid2, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children, int lineno, int charno) { this(nodeType, children); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children) { this.type = nodeType; parent = null; if (children.length != 0) { this.first = children[0]; this.last = children[children.length - 1]; for (int i = 1; i < children.length; i++) { if (null != children[i - 1].next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } children[i - 1].next = children[i]; Preconditions.checkArgument(children[i - 1].parent == null); children[i - 1].parent = this; } Preconditions.checkArgument(children[children.length - 1].parent == null); children[children.length - 1].parent = this; if (null != this.last.next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } } } public static Node newNumber(double number) { return new NumberNode(number); } public static Node newNumber(double number, int lineno, int charno) { return new NumberNode(number, lineno, charno); } public static Node newString(String str) { return new StringNode(Token.STRING, str); } public static Node newString(int type, String str) { return new StringNode(type, str); } public static Node newString(String str, int lineno, int charno) { return new StringNode(Token.STRING, str, lineno, charno); } public static Node newString(int type, String str, int lineno, int charno) { return new StringNode(type, str, lineno, charno); } public int getType() { return type; } public void setType(int type) { this.type = type;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } public boolean hasChildren() { return first != null; } public Node getFirstChild() { return first; } public Node getLastChild() { return last; } public Node getNext() { return next; } public Node getChildBefore(Node child) { if (child == first) { return null; } Node n = first; while (n.next != child) { n = n.next; if (n == null) { throw new RuntimeException("node is not a child"); } } return n; } public Node getChildAtIndex(int i) { Node n = first; while (i > 0) { n = n.next; i--; } return n; } public Node getLastSibling() { Node n = this; while (n.next != null) { n = n.next; } return n; } public void addChildToFront(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = first; first = child; if (last == null) { last = child; } } public void addChildToBack(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = null; if (last == null) { first = last = child; return; } last.next = child; last = child; } public void addChildrenToFront(Node children) { for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } Node lastSib = children.getLastSibling(); lastSib.next = first; first = children; if (last == null) { last = lastSib; } } public void addChildrenToBack(Node children) { for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } if (last != null) { last.next = children; } last = children.getLastSibling(); if (first == null) { first = children; } } /** * Add 'child' before 'node'. */ public void addChildBefore(Node newChild, Node node) { Preconditions.checkArgument(node != null, "The existing child node of the parent should not be null."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); if (first == node) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> newChild.parent = this; newChild.next = first; first = newChild; return; } Node prev = getChildBefore(node); addChildAfter(newChild, prev); } /** * Add 'child' after 'node'. */ public void addChildAfter(Node newChild, Node node) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); newChild.parent = this; newChild.next = node.next; node.next = newChild; if (last == node) { last = newChild; } } /** * Detach a child from its parent and siblings. */ public void removeChild(Node child) { Node prev = getChildBefore(child); if (prev == null) first = first.next; else prev.next = child.next; if (child == last) last = prev; child.next = null; child.parent = null; } /** * Detaches child from Node and replaces it with newChild. */ public void replaceChild(Node child, Node newChild) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(child); newChild.next = child.next; newChild.parent = this; if (child == first) { first = newChild; } else { Node prev = getChildBefore(child); prev.next = newChild; } if (child == last) last = newChild; child.next = null; child.parent = null; } public void replaceChildAfter(Node prevChild, Node newChild) { Preconditions.checkArgument(prevChild.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(prevChild); Node child = prevChild.next; newChild.next = child.next; newChild.parent = this; prevChild.next = newChild; if (child == last) last = newChild; child.next = null; child.parent = null; } @VisibleForTesting PropListItem lookupProperty(int propType) { PropListItem x = propListHead; while (x != null && propType != x.type) { x = x.next; } return x; } /** * Clone the properties

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> from the provided node without copying * the property object. The recieving node may not have any * existing properties. * @param other The node to clone properties from. * @return this node. */ public Node clonePropsFrom(Node other) { Preconditions.checkState(this.propListHead == null, "Node has existing properties."); this.propListHead = other.propListHead; return this; } public void removeProp(int propType) { PropListItem result = removeProp(propListHead, propType); if (result != propListHead) { propListHead = result; } } /** * @param item The item to inspect * @param propType The property to look for * @return The replacement list if the property was removed, or * 'item' otherwise. */ private PropListItem removeProp(PropListItem item, int propType) { if (item == null) { return null; } else if (item.type == propType) { return item.next; } else { PropListItem result = removeProp(item.next, propType); if (result != item.next) { return new PropListItem( item.type, item.intValue, item.objectValue, result); } else { return item; } } } public Object getProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return null; } return item.objectValue; } public boolean getBooleanProp(int propType) { return getIntProp(propType) != 0; } /** * Returns the integer value for the property, or 0 if the property * is not defined. */ public int getIntProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return 0; } return item.intValue; } public int getExistingIntProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { Kit.codeBug(); } return item.intValue; } public void putProp(int propType, Object value) { removeProp(propType); if (value != null) { propListHead = new PropListItem(propType, value, propListHead); } } public void putBooleanProp(int propType, boolean value) { putIntProp(propType, value ? 1 : 0); } public void putIntProp(int propType, int value) { removeProp(propType); if (value != 0) { propListHead = new PropListItem(propType, value, propListHead); } } // Gets all the property types, in sorted order. private int[] getSortedPropTypes() { int count = 0; for (PropListItem x = prop

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>ListHead; x != null; x = x.next) { count++; } int[] keys = new int[count]; for (PropListItem x = propListHead; x != null; x = x.next) { count--; keys[count] = x.type; } Arrays.sort(keys); return keys; } public int getLineno() { return extractLineno(sourcePosition); } public int getCharno() { return extractCharno(sourcePosition); } /** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */ public double getDouble() throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException(this + " is not a number node"); } } /** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */ public void setDouble(double s) throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } /** Can only be called when node has String context. */ public String getString() throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } /** Can only be called when node has String context. */ public void setString(String s) throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } @Override public String toString() { return toString(true, true, true); } public String toString( boolean printSource, boolean printAnnotations, boolean printType) { if (Token.printTrees) { StringBuilder sb = new StringBuilder(); toString(sb, printSource, printAnnotations, printType); return sb.toString(); } return String.valueOf(type); } private void toString( StringBuilder sb, boolean printSource, boolean printAnnotations, boolean printType) { if (Token.printTrees) { sb.append(Token.name(type)); if (this instanceof StringNode) { sb.append(' '); sb.append(getString()); } else if (type == Token.FUNCTION) { sb.append(' '); // In the case of JsDoc trees, the first child is often not a string // which causes exceptions to be thrown when calling toString or // toStringTree. if (first.getType() == Token.STRING) { sb.append(first.getString()); }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } else if (this instanceof ScriptOrFnNode) { ScriptOrFnNode sof = (ScriptOrFnNode) this; if (this instanceof FunctionNode) { FunctionNode fn = (FunctionNode) this; sb.append(' '); sb.append(fn.getFunctionName()); } if (printSource) { sb.append(" [source name: "); sb.append(sof.getSourceName()); sb.append("] [encoded source length: "); sb.append(sof.getEncodedSourceEnd() - sof.getEncodedSourceStart()); sb.append("] [base line: "); sb.append(sof.getBaseLineno()); sb.append("] [end line: "); sb.append(sof.getEndLineno()); sb.append(']'); } } else if (type == Token.NUMBER) { sb.append(' '); sb.append(getDouble()); } if (printSource) { int lineno = getLineno(); if (lineno != -1) { sb.append(' '); sb.append(lineno); } } if (printAnnotations) { int[] keys = getSortedPropTypes(); for (int i = 0; i < keys.length; i++) { int type = keys[i]; PropListItem x = lookupProperty(type); sb.append(" ["); sb.append(propToString(type)); sb.append(": "); String value; switch (type) { case TARGETBLOCK_PROP: // can't add this as it recurses value = "target block property"; break; case LOCAL_BLOCK_PROP: // can't add this as it is dull value = "last local block"; break; case ISNUMBER_PROP: switch (x.intValue) { case BOTH: value = "both"; break; case RIGHT: value = "right"; break; case LEFT: value = "left"; break; default: throw Kit.codeBug(); } break; case SPECIALCALL_PROP: switch (x.intValue) { case SPECIALCALL_EVAL: value = "eval"; break; case SPECIALCALL_WITH: value = "with"; break; default: // NON_SPECIALCALL should not be stored throw Kit.codeBug(); } break; default: Object obj = x.objectValue; if (obj != null) { value = obj.toString(); } else { value = String.valueOf(x.intValue); } break; } sb.append(value); sb.append(']'); } } if (printType) { if (jsType != null) { String jsTypeString = jsType.toString(); if (jsTypeString != null) { sb.append(" : "); sb.append(jsTypeString); } } } } } public String toStringTree() { return toStringTreeImpl();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } private String toStringTreeImpl() { try { StringBuilder s = new StringBuilder(); appendStringTree(s); return s.toString(); } catch (IOException e) { throw new RuntimeException("Should not happen\n" + e); } } public void appendStringTree(Appendable appendable) throws IOException { toStringTreeHelper(this, 0, appendable); } private static void toStringTreeHelper(Node n, int level, Appendable sb) throws IOException { if (Token.printTrees) { for (int i = 0; i != level; ++i) { sb.append(" "); } sb.append(n.toString()); sb.append('\n'); for (Node cursor = n.getFirstChild(); cursor != null; cursor = cursor.getNext()) { toStringTreeHelper(cursor, level + 1, sb); } } } int type; // type of the node; Token.NAME for example Node next; // next sibling private Node first; // first element of a linked list of children private Node last; // last element of a linked list of children /** * Linked list of properties. Since vast majority of nodes would have * no more then 2 properties, linked list saves memory and provides * fast lookup. If this does not holds, propListHead can be replaced * by UintMap. */ private PropListItem propListHead; /** * COLUMN_BITS represents how many of the lower-order bits of * sourcePosition are reserved for storing the column number. * Bits above these store the line number. * This gives us decent position information for everything except * files already passed through a minimizer, where lines might * be longer than 4096 characters. */ public static final int COLUMN_BITS = 12; /** * MAX_COLUMN_NUMBER represents the maximum column number that can * be represented. JSCompiler's modifications to Rhino cause all * tokens located beyond the maximum column to MAX_COLUMN_NUMBER. */ public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1; /** * COLUMN_MASK stores a value where bits storing the column number * are set, and bits storing the line are not set. It's handy for * separating column number from line number. */ public static final int COLUMN_MASK = MAX_COLUMN_NUMBER; /** * Source position of this node. The position is encoded with the * column number in the low 12 bits of the integer, and the line * number in the rest. Create some handy constants so we can change this * size if we want. */ private int sourcePosition; private JSType jsType; private Node parent; //========================================================================== // Source position management public void setLineno(int lineno) { int charno = getCharno(); if (charno == -1) { charno =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>NodeIterable implements Iterable<Node>, Iterator<Node> { private final Node start; private Node current; private boolean used; SiblingNodeIterable(Node start) { this.start = start; this.current = start; this.used = false; } public Iterator<Node> iterator() { if (!used) { used = true; return this; } else { // We have already used the current object as an iterator; // we must create a new SiblingNodeIterable based on this // iterable's start node. // // Since the primary use case for Node.children is in for // loops, this branch is extremely unlikely. return (new SiblingNodeIterable(start)).iterator(); } } public boolean hasNext() { return current != null; } public Node next() { if (current == null) { throw new NoSuchElementException(); } try { return current; } finally { current = current.getNext(); } } public void remove() { throw new UnsupportedOperationException(); } } // ========================================================================== // Accessors public Node getParent() { return parent; } /** * Gets the ancestor node relative to this. * * @param level 0 = this, 1 = the parent, etc. */ public Node getAncestor(int level) { Preconditions.checkArgument(level >= 0); Node node = this; while (node != null && level-- > 0) { node = node.getParent(); } return node; } /** * Iterates all of the node's ancestors excluding itself. */ public AncestorIterable getAncestors() { return new AncestorIterable(this.getParent()); } /** * Iterator to go up the ancestor tree. */ public static class AncestorIterable implements Iterable<Node> { private Node cur; /** * @param cur The node to start. */ AncestorIterable(Node cur) { this.cur = cur; } public Iterator<Node> iterator() { return new Iterator<Node>() { public boolean hasNext() { return cur != null; } public Node next() { if (!hasNext()) throw new NoSuchElementException(); Node n = cur; cur = cur.getParent(); return n; } public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Check for one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * * @return Whether the node has exactly one child. */ public boolean hasOneChild() { return first != null && first == last; } /** * Check for more than one child more efficiently than by iterating over all * the children as is done with Node.getChildCount(). * * @return Whether the node more than one child. */ public boolean hasMoreThanOneChild() { return first != null

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> && first != last; } public int getChildCount() { int c = 0; for (Node n = first; n != null; n = n.next) c++; return c; } // Intended for testing and verification only. public boolean hasChild(Node child) { for (Node n = first; n != null; n = n.getNext()) { if (child == n) { return true; } } return false; } /** * Checks if the subtree under this node is the same as another subtree. * Returns null if it's equal, or a message describing the differences. */ public String checkTreeEquals(Node node2) { NodeMismatch diff = checkTreeEqualsImpl(node2); if (diff != null) { return "Node tree inequality:" + "\nTree1:\n" + toStringTree() + "\n\nTree2:\n" + node2.toStringTree() + "\n\nSubtree1: " + diff.nodeA.toStringTree() + "\n\nSubtree2: " + diff.nodeB.toStringTree(); } return null; } /** * If this is a compilation pass and not a test, do not construct error * strings. Instead return true if the trees are equal. */ public boolean checkTreeEqualsSilent(Node node2) { return checkTreeEqualsImpl(node2) == null; } /** * Helper function to ignore differences in Node subclasses that are no longer * used. */ @SuppressWarnings("unchecked") static private Class getNodeClass(Node n) { Class c = n.getClass(); if (c == FunctionNode.class || c == ScriptOrFnNode.class) { return Node.class; } return c; } /** * Compare this node to node2 recursively and return the first pair of nodes * that differs doing a preorder depth-first traversal. Package private for * testing. Returns null if the nodes are equivalent. */ NodeMismatch checkTreeEqualsImpl(Node node2) { boolean eq = false; if (type == node2.getType() && getChildCount() == node2.getChildCount() && getNodeClass(this) == getNodeClass(node2)) { eq = this.isEquivalentTo(node2); } if (!eq) { return new NodeMismatch(this, node2); } NodeMismatch res = null; Node n, n2; for (n = first, n2 = node2.first; res == null && n != null; n = n.next, n2 = n2.next) { res = n.checkTreeEqualsImpl(n2); if (res != null) { return res; } } return res; } /** * Checks if the subtree under this node is the same as another subtree * including types. Returns null if it's equal, or a message describing

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> the * differences. */ public boolean checkTreeTypeAwareEqualsSilent(Node node2) { return checkTreeTypeAwareEqualsImpl(node2) == null; } /** * Compare this node to node2 recursively and return the first pair of nodes * that differs doing a preorder depth-first traversal. Package private for * testing. Returns null if the nodes are equivalent. */ NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) { boolean eq = false; if (type == node2.getType() && getChildCount() == node2.getChildCount() && getClass() == node2.getClass() && JSType.isEquivalent(jsType, node2.getJSType())) { eq = this.isEquivalentTo(node2); } if (!eq) { return new NodeMismatch(this, node2); } NodeMismatch res = null; Node n, n2; for (n = first, n2 = node2.first; res == null && n != null; n = n.next, n2 = n2.next) { res = n.checkTreeTypeAwareEqualsImpl(n2); if (res != null) { return res; } } return res; } public static String tokenToName(int token) { switch (token) { case Token.ERROR: return "error"; case Token.EOF: return "eof"; case Token.EOL: return "eol"; case Token.ENTERWITH: return "enterwith"; case Token.LEAVEWITH: return "leavewith"; case Token.RETURN: return "return"; case Token.GOTO: return "goto"; case Token.IFEQ: return "ifeq"; case Token.IFNE: return "ifne"; case Token.SETNAME: return "setname"; case Token.BITOR: return "bitor"; case Token.BITXOR: return "bitxor"; case Token.BITAND: return "bitand"; case Token.EQ: return "eq"; case Token.NE: return "ne"; case Token.LT: return "lt"; case Token.LE: return "le"; case Token.GT: return "gt"; case Token.GE: return "ge"; case Token.LSH: return "lsh"; case Token.RSH: return "rsh"; case Token.URSH: return "ursh"; case Token.ADD: return "add"; case Token.SUB: return "sub"; case Token.MUL: return "mul"; case Token.DIV: return "div"; case Token.MOD: return "mod"; case Token.BITNOT: return "bitnot"; case Token.NEG: return "neg"; case Token.NEW: return "new"; case Token.DELPROP: return "del

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>prop"; case Token.TYPEOF: return "typeof"; case Token.GETPROP: return "getprop"; case Token.SETPROP: return "setprop"; case Token.GETELEM: return "getelem"; case Token.SETELEM: return "setelem"; case Token.CALL: return "call"; case Token.NAME: return "name"; case Token.NUMBER: return "number"; case Token.STRING: return "string"; case Token.NULL: return "null"; case Token.THIS: return "this"; case Token.FALSE: return "false"; case Token.TRUE: return "true"; case Token.SHEQ: return "sheq"; case Token.SHNE: return "shne"; case Token.REGEXP: return "regexp"; case Token.POS: return "pos"; case Token.BINDNAME: return "bindname"; case Token.THROW: return "throw"; case Token.IN: return "in"; case Token.INSTANCEOF: return "instanceof"; case Token.GETVAR: return "getvar"; case Token.SETVAR: return "setvar"; case Token.TRY: return "try"; case Token.TYPEOFNAME: return "typeofname"; case Token.THISFN: return "thisfn"; case Token.SEMI: return "semi"; case Token.LB: return "lb"; case Token.RB: return "rb"; case Token.LC: return "lc"; case Token.RC: return "rc"; case Token.LP: return "lp"; case Token.RP: return "rp"; case Token.COMMA: return "comma"; case Token.ASSIGN: return "assign"; case Token.ASSIGN_BITOR: return "assign_bitor"; case Token.ASSIGN_BITXOR: return "assign_bitxor"; case Token.ASSIGN_BITAND: return "assign_bitand"; case Token.ASSIGN_LSH: return "assign_lsh"; case Token.ASSIGN_RSH: return "assign_rsh"; case Token.ASSIGN_URSH: return "assign_ursh"; case Token.ASSIGN_ADD: return "assign_add"; case Token.ASSIGN_SUB: return "assign_sub"; case Token.ASSIGN_MUL: return "assign_mul"; case Token.ASSIGN_DIV: return "assign_div"; case Token.ASSIGN_MOD: return "assign_mod"; case Token.HOOK: return "hook"; case Token.COLON: return "colon"; case Token.OR: return "or"; case Token.AND: return "and"; case Token.INC: return "inc"; case Token.DEC: return "dec"; case Token.DOT: return "

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>dot"; case Token.FUNCTION: return "function"; case Token.EXPORT: return "export"; case Token.IMPORT: return "import"; case Token.IF: return "if"; case Token.ELSE: return "else"; case Token.SWITCH: return "switch"; case Token.CASE: return "case"; case Token.DEFAULT: return "default"; case Token.WHILE: return "while"; case Token.DO: return "do"; case Token.FOR: return "for"; case Token.BREAK: return "break"; case Token.CONTINUE: return "continue"; case Token.VAR: return "var"; case Token.WITH: return "with"; case Token.CATCH: return "catch"; case Token.FINALLY: return "finally"; case Token.RESERVED: return "reserved"; case Token.NOT: return "not"; case Token.VOID: return "void"; case Token.BLOCK: return "block"; case Token.ARRAYLIT: return "arraylit"; case Token.OBJECTLIT: return "objectlit"; case Token.LABEL: return "label"; case Token.TARGET: return "target"; case Token.LOOP: return "loop"; case Token.EXPR_VOID: return "expr_void"; case Token.EXPR_RESULT: return "expr_result"; case Token.JSR: return "jsr"; case Token.SCRIPT: return "script"; case Token.EMPTY: return "empty"; case Token.GET_REF: return "get_ref"; case Token.REF_SPECIAL: return "ref_special"; } return "<unknown="+token+">"; } /** Returns true if this node is equivalent semantically to another */ public boolean isEquivalentTo(Node node) { if (type == Token.ARRAYLIT) { try { int[] indices1 = (int[]) getProp(Node.SKIP_INDEXES_PROP); int[] indices2 = (int[]) node.getProp(Node.SKIP_INDEXES_PROP); if (indices1 == null) { if (indices2 != null) { return false; } } else if (indices2 == null) { return false; } else if (indices1.length != indices2.length) { return false; } else { for (int i = 0; i < indices1.length; i++) { if (indices1[i] != indices2[i]) { return false; } } } } catch (Exception e) { return false; } } else if (type == Token.INC || type == Token.DEC) { int post1 = this.getIntProp(INCRDECR_PROP); int post2 = node.getIntProp(INCRDECR_PROP); if (post1 != post2) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return false; } } else if (type == Token.STRING) { int quoted1 = this.getIntProp(QUOTED_PROP); int quoted2 = node.getIntProp(QUOTED_PROP); if (quoted1 != quoted2) { return false; } } return true; } public boolean hasSideEffects() { switch (type) { case Token.EXPR_VOID: case Token.COMMA: if (last != null) return last.hasSideEffects(); else return true; case Token.HOOK: if (first == null || first.next == null || first.next.next == null) { Kit.codeBug(); } return first.next.hasSideEffects() && first.next.next.hasSideEffects(); case Token.ERROR: // Avoid cascaded error messages case Token.EXPR_RESULT: case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ENTERWITH: case Token.LEAVEWITH: case Token.RETURN: case Token.GOTO: case Token.IFEQ: case Token.IFNE: case Token.NEW: case Token.DELPROP: case Token.SETNAME: case Token.SETPROP: case Token.SETELEM: case Token.CALL: case Token.THROW: case Token.RETHROW: case Token.SETVAR: case Token.CATCH_SCOPE: case Token.RETURN_RESULT: case Token.SET_REF: case Token.DEL_REF: case Token.REF_CALL: case Token.TRY: case Token.SEMI: case Token.INC: case Token.DEC: case Token.EXPORT: case Token.IMPORT: case Token.IF: case Token.ELSE: case Token.SWITCH: case Token.WHILE: case Token.DO: case Token.FOR: case Token.BREAK: case Token.CONTINUE: case Token.VAR: case Token.CONST: case Token.WITH: case Token.CATCH: case Token.FINALLY: case Token.BLOCK: case Token.LABEL: case Token.TARGET: case Token.LOOP: case Token.JSR: case Token.SETPROP_OP: case Token.SETELEM_OP: case Token.LOCAL_BLOCK: case Token.SET_REF_OP: return true; default: return false; } } /** * This function takes a set of GETPROP nodes and produces a string that is * each property separated by dots. If the node

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> ultimately under the left * sub-tree is not a simple name, this is not a valid qualified name. * * @return a null if this is not a qualified name, or a dot-separated string * of the name and properties. */ public String getQualifiedName() { if (type == Token.NAME) { return getString(); } else if (type == Token.GETPROP) { String left = getFirstChild().getQualifiedName(); if (left == null) { return null; } return left + "." + getLastChild().getString(); } else if (type == Token.THIS) { return "this"; } else { return null; } } /** * Returns whether a node corresponds to a simple or a qualified name, such as * <code>x</code> or <code>a.b.c</code> or <code>this.a</code>. */ public boolean isQualifiedName() { switch (getType()) { case Token.NAME: case Token.THIS: return true; case Token.GETPROP: return getFirstChild().isQualifiedName(); default: return false; } } /** * Returns whether a node corresponds to a simple or a qualified name without * a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code> * . */ public boolean isUnscopedQualifiedName() { switch (getType()) { case Token.NAME: return true; case Token.GETPROP: return getFirstChild().isUnscopedQualifiedName(); default: return false; } } // ========================================================================== // Mutators /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public Node detachFromParent() { Preconditions.checkState(parent != null); parent.removeChild(this); return this; } /** * Removes the first child of Node. Equivalent to: * node.removeChild(node.getFirstChild()); * * @return The removed Node. */ public Node removeFirstChild() { Node child = first; if (child != null) { removeChild(child); } return child; } /** * @return A Node that is the head of the list of children. */ public Node removeChildren() { Node children = first; for (Node child = first; child != null; child = child.getNext()) { child.parent = null; } first = null; last = null; return children; } /** * Removes all children from this node and isolates the children from each * other. */ public void detachChildren() { for (Node child = first; child != null;) { Node nextChild = child.getNext(); child.parent = null; child.next = null; child = nextChild; } first = null; last =

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> null; } public Node removeChildAfter(Node prev) { Preconditions.checkArgument(prev.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(prev.next != null, "no next sibling."); Node child = prev.next; prev.next = child.next; if (child == last) last = prev; child.next = null; child.parent = null; return child; } /** * @return A detached clone of the Node, specifically excluding its children. */ public Node cloneNode() { Node result; try { result = (Node) super.clone(); // PropListItem lists are immutable and can be shared so there is no // need to clone them here. result.next = null; result.first = null; result.last = null; result.parent = null; } catch (CloneNotSupportedException e) { throw new RuntimeException(e.getMessage()); } return result; } /** * @return A detached clone of the Node and all its children. */ public Node cloneTree() { Node result = cloneNode(); for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) { Node n2clone = n2.cloneTree(); n2clone.parent = result; if (result.last != null) { result.last.next = n2clone; } if (result.first == null) { result.first = n2clone; } result.last = n2clone; } return result; } /** * Copies source file and name information from the other * node given to the current node. Used for maintaining * debug information across node append and remove operations. * @return this */ public Node copyInformationFrom(Node other) { if (getProp(ORIGINALNAME_PROP) == null) { putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP)); } if (getProp(SOURCENAME_PROP) == null) { putProp(SOURCENAME_PROP, other.getProp(SOURCENAME_PROP)); sourcePosition = other.sourcePosition; } return this; } /** * Copies source file and name information from the other node to the * entire tree rooted at this node. * @return this */ public Node copyInformationFromForTree(Node other) { copyInformationFrom(other); for (Node child = getFirstChild(); child != null; child = child.getNext()) { child.copyInformationFromForTree(other); } return this; } //========================================================================== // Custom annotations public JSType getJSType() { return jsType; } public void setJSType(JSType jsType) { this.jsType = jsType; } public FileLevelJsDocBuilder get

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> flags) { Preconditions.checkArgument( getType() == Token.CALL || getType() == Token.NEW, "setIsNoSideEffectsCall only supports CALL and NEW nodes, got " + Token.name(getType())); putIntProp(SIDE_EFFECT_FLAGS, flags); } public void setSideEffectFlags(SideEffectFlags flags) { setSideEffectFlags(flags.valueOf()); } /** * Returns the side effects flags for this node. */ public int getSideEffectFlags() { return getIntProp(SIDE_EFFECT_FLAGS); } /** * A helper class for getting and setting the side-effect flags. * @author johnlenz@google.com (John Lenz) */ public static class SideEffectFlags { private int value = Node.SIDE_EFFECTS_ALL; public SideEffectFlags() { } public SideEffectFlags(int value) { this.value = value; } public int valueOf() { return value; } /** All side-effect occur and the returned results are non-local. */ public void setAllFlags() { value = Node.SIDE_EFFECTS_ALL; } /** No side-effects occur and the returned results are local. */ public void clearAllFlags() { value = Node.NO_SIDE_EFFECTS | Node.FLAG_LOCAL_RESULTS; } public boolean areAllFlagsSet() { return value == Node.SIDE_EFFECTS_ALL; } /** * Preserve the return result flag, but clear the others: * no global state change, no throws, no this change, no arguments change */ public void clearSideEffectFlags() { value |= Node.NO_SIDE_EFFECTS; } public void setMutatesGlobalState() { // Modify global means everything must be assumed to be modified. removeFlag(Node.FLAG_GLOBAL_STATE_UNMODIFIED); removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED); removeFlag(Node.FLAG_THIS_UNMODIFIED); } public void setThrows() { removeFlag(Node.FLAG_NO_THROWS); } public void setMutatesThis() { removeFlag(Node.FLAG_THIS_UNMODIFIED); } public void setMutatesArguments() { removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED); } public void setReturnsTainted() { removeFlag(Node.FLAG_LOCAL_RESULTS); } private void removeFlag(int flag) { value &= ~flag; } } /** * @return Whether the only side-effect is "modifies this" */ public boolean isOnlyModifiesThisCall() { return areBitFlagsSet( getSideEffectFlags() & Node.NO_SIDE_EFFECTS, Node.FLAG_GLOBAL_STATE_UNMODIFIED | Node.FLAG_ARGUMENTS_UNMODIFIED | Node.FLAG_NO_THROWS);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import com.google.common.base.Preconditions; /** * An object type that is an instance of some function constructor. */ public final class InstanceObjectType extends PrototypeObjectType { private static final long serialVersionUID = 1L; private final FunctionType constructor; InstanceObjectType(JSTypeRegistry registry, FunctionType constructor) { this(registry, constructor, false); } InstanceObjectType(JSTypeRegistry registry, FunctionType constructor, boolean isNativeType) { super(registry, null, null, isNativeType); Preconditions.checkNotNull(constructor); this.constructor = constructor; } @Override public String getReferenceName() { return getConstructor().getReferenceName(); } @Override public boolean hasReferenceName() { return getConstructor().hasReferenceName(); } @Override public ObjectType getImplicitPrototype() { return getConstructor().getPrototype(); } @Override public FunctionType getConstructor() { return constructor; } @Override boolean defineProperty(String name, JSType type, boolean inferred, boolean inExterns) { ObjectType proto = getImplicitPrototype(); if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Controls.VISIBILITY_MISMATCH)); public static DiagnosticGroup NON_STANDARD_JSDOC = DiagnosticGroups.registerGroup("nonStandardJsDocs", new DiagnosticGroup(RhinoErrorReporter.BAD_JSDOC_ANNOTATION)); public static DiagnosticGroup ACCESS_CONTROLS = DiagnosticGroups.registerGroup("accessControls", new DiagnosticGroup(DEPRECATED, VISIBILITY)); public static DiagnosticGroup INVALID_CASTS = DiagnosticGroups .registerGroup("invalidCasts", new DiagnosticGroup(TypeValidator.INVALID_CAST)); public static DiagnosticGroup FILEOVERVIEW_JSDOC = DiagnosticGroups.registerGroup("fileoverviewTags", new DiagnosticGroup(RhinoErrorReporter.EXTRA_FILEOVERVIEW)); public static DiagnosticGroup STRICT_MODULE_DEP_CHECK = DiagnosticGroups.registerGroup("strictModuleDepCheck", new DiagnosticGroup(VarCheck.STRICT_MODULE_DEP_ERROR, CheckGlobalNames.STRICT_MODULE_DEP_QNAME)); public static DiagnosticGroup EXTERNS_VALIDATION = DiagnosticGroups.registerGroup("externsValidation", new DiagnosticGroup(VarCheck.NAME_REFERENCE_IN_EXTERNS_ERROR, VarCheck.UNDEFINED_EXTERN_VAR_ERROR)); public static DiagnosticGroup AMBIGUOUS_FUNCTION_DECL = DiagnosticGroups.registerGroup("ambiguousFunctionDecl", new DiagnosticGroup(VariableReferenceCheck.AMBIGUOUS_FUNCTION_DECL)); public static DiagnosticGroup UNKNOWN_DEFINES = DiagnosticGroups.registerGroup("unknownDefines", new DiagnosticGroup(ProcessDefines.UNKNOWN_DEFINE_WARNING)); public static DiagnosticGroup MISSING_PROPERTIES = DiagnosticGroups.registerGroup("missingProperties", new DiagnosticGroup(TypeCheck.INEXISTENT_PROPERTY)); public static DiagnosticGroup UNDEFINED_VARIABLES = DiagnosticGroups.registerGroup("undefinedVars", new DiagnosticGroup(VarCheck.UNDEFINED_VAR_ERROR)); public static DiagnosticGroup CHECK_REGEXP = DiagnosticGroups.registerGroup("checkRegExp", new DiagnosticGroup( CheckRegExp.REGEXP_REFERENCE)); public static DiagnosticGroup CHECK_TYPES = DiagnosticGroups.registerGroup("checkTypes", new DiagnosticGroup( TypeValidator.ALL_DIAGNOSTICS, TypeCheck.ALL_DIAGNOSTICS)); public static DiagnosticGroup CHECK_VARIABLES = DiagnosticGroups.registerGroup("checkVars", new DiagnosticGroup( VarCheck.UNDEFINED_VAR_ERROR, SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR)); public static DiagnosticGroup CHECK_USELESS_CODE = DiagnosticGroups.registerGroup("uselessCode", new DiagnosticGroup( CheckSideEffects.USELESS_CODE_ERROR, CheckUnreachableCode.UNREACHABLE_CODE)); /** * Adds warning levels by name. */ void setWarningLevels(CompilerOptions options, List<String> diagnosticGroups, CheckLevel level) { for (String name : diagnosticGroups) { DiagnosticGroup group = forName(name); Preconditions.checkNotNull(group, "No warning class for name: " + name); options.set

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * pass introduces */ OptimizeArgumentsArray(AbstractCompiler compiler, String paramPrefix) { this.compiler = Preconditions.checkNotNull(compiler); this.paramPredix = Preconditions.checkNotNull(paramPrefix); } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, Preconditions.checkNotNull(root), this); } @Override public void enterScope(NodeTraversal traversal) { Preconditions.checkNotNull(traversal); // This optimization is valid only within a function so we are going to // skip over the initial entry to the global scope. Node function = traversal.getScopeRoot(); if (!NodeUtil.isFunction(function)) { return; } // Introduces a new access list and stores the access list of the outer // scope in the stack if necessary. if (currentArgumentsAccess != null) { argumentsAccessStack.push(currentArgumentsAccess); } currentArgumentsAccess = Lists.newLinkedList(); } @Override public void exitScope(NodeTraversal traversal) { Preconditions.checkNotNull(traversal); // This is the case when we are exiting the global scope where we had never // collected argument access list. Since we do not perform this optimization // for the global scope, we will skip this exit point. if (currentArgumentsAccess == null) { return; } // Attempt to replace the argument access and if the AST has been change, // report back to the compiler. if (tryReplaceArguments(traversal.getScope())) { traversal.getCompiler().reportCodeChange(); } // After the attempt to replace the arguments. The currentArgumentsAccess // is stale and as we exit the Scope, no longer holds all the access to the // current scope anymore. We'll pop the access list from the outer scope // and set it as currentArgumentsAcess if the outer scope is not the global // scope. if (!argumentsAccessStack.isEmpty()) { currentArgumentsAccess = argumentsAccessStack.pop(); } else { currentArgumentsAccess = null; } } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node node, Node parent) { // We will continuously recurse down the AST regardless of the node types. return true; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { Preconditions.checkNotNull(traversal); Preconditions.checkNotNull(node); // Searches for all the references to the arguments array. // We don't have an arguments list set up for this scope. This implies we // are currently in the global scope so we will not record any arguments // array access. if (currentArgumentsAccess == null) { return; } // Otherwise, we are in a function scope and we should record if the current // name is referring to the implicit arguments array. if (NodeUtil.isName(node) && ARGUMENTS.equals(node.getString())) { currentArgumentsAccess.add(node); } } /** * Tries

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> to optimize all the arguments array access in this scope by assigning * a name to each element. * * @param scope scope of the function * @return true if any modification has been done to the AST */ private boolean tryReplaceArguments(Scope scope) { Node parametersList = scope.getRootNode().getFirstChild().getNext(); Preconditions.checkState(parametersList.getType() == Token.LP); // Keep track of rather this function modified the AST and needs to be // reported back to the compiler later. boolean changed = false; // Number of parameter that can be accessed without using the arguments // array. int numNamedParameter = parametersList.getChildCount(); // We want to guess what the highest index that has been access from the // arguments array. We will guess that it does not use anything index higher // than the named parameter list first until we see other wise. int highestIndex = numNamedParameter - 1; // Iterate through all the references to arguments array in the function to // determine the real highestIndex. for (Node ref : currentArgumentsAccess) { Node getElem = ref.getParent(); // Bail on anything but argument[c] access where c is a constant. // TODO(user): We might not need to bail out all the time, there might // be more cases that we can cover. if (getElem.getType() != Token.GETELEM) { return false; } Node index = ref.getNext(); // We have something like arguments[x] where x is not a constant. That // means at least one of the access is not known. if (index.getType() != Token.NUMBER) { // TODO(user): Its possible not to give up just yet. The type // inference did a 'semi value propagation'. If we know that string // is never a subclass of the type of the index. We'd know that // it is never 'callee'. return false; // Give up. } Node getElemParent = getElem.getParent(); // When we have argument[0](), replacing it with a() is semantically // different if argument[0] is a function call that refers to 'this' if (NodeUtil.isCall(getElemParent) && getElemParent.getFirstChild() == getElem) { // TODO(user): We can consider using .call() if aliasing that // argument allows shorter alias for other arguments. return false; } // Replace the highest index if we see an access that has a higher index // than all the one we saw before. int value = (int) index.getDouble(); if (value > highestIndex) { highestIndex = value; } } // Number of extra arguments we need. // For example: function() { arguments[3] } access index 3 so // it will need 4 extra named arguments to changed into: // function(a,b,c,d) { d }. int numExtraArgs = highestIndex - numNamedParameter + 1

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>; // Temporary holds the new names as string for quick access later. String[] argNames = new String[numExtraArgs]; // Insert the formal parameter to the method's signature. // Example: function() --> function(r0, r1, r2) for (int i = 0; i < numExtraArgs; i++) { String name = getNewName(); argNames[i] = name; parametersList.addChildrenToBack(Node.newString(Token.NAME, name)); changed = true; } // This loop performs the replacement of arguments[x] -> a if x is known. for (Node ref : currentArgumentsAccess) { Node index = ref.getNext(); // Skip if it is unknown. if (index.getType() != Token.NUMBER) { continue; } int value = (int) index.getDouble(); // Unnamed parameter. if (value >= numNamedParameter) { ref.getParent().getParent().replaceChild(ref.getParent(), Node.newString(Token.NAME, argNames[value - numNamedParameter])); } else { // Here, for no apparent reason, the user is accessing a named parameter // with arguments[idx]. We can replace it with the actual name for them. Node name = parametersList.getFirstChild(); // This is a linear search for the actual name from the signature. // It is not necessary to make this fast because chances are the user // will not deliberately write code like this. for (int i = 0; i < value; i++) { name = name.getNext(); } ref.getParent().getParent().replaceChild(ref.getParent(), Node.newString(Token.NAME, name.getString())); } changed = true; } return changed; } /** * Generate a unique name for the next parameter. */ private String getNewName() { return paramPredix + uniqueId++; } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Branch> entry = cfg.getEntry(); prioritizeFromEntryNode(entry); if (shouldTraverseFunctions) { // If we're traversing inner functions, we need to rank the // priority of them too. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { Node value = candidate.getValue(); if (value != null && value.getType() == Token.FUNCTION) { Preconditions.checkState( !nodePriorities.containsKey(candidate) || candidate == entry); prioritizeFromEntryNode(candidate); } } } // At this point, all reachable nodes have been given a priority, but // unreachable nodes have not been given a priority. Put them last. // Presumably, it doesn't really matter what priority they get, since // this shouldn't happen in real code. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { if (!nodePriorities.containsKey(candidate)) { nodePriorities.put(candidate, ++priorityCounter); } } // Again, the implicit return node is always last. nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter); } /** * Given an entry node, find all the nodes reachable from that node * and prioritize them. */ private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) { PriorityQueue<DiGraphNode<Node, Branch>> worklist = new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator); worklist.add(entry); while (!worklist.isEmpty()) { DiGraphNode<Node, Branch> current = worklist.remove(); if (nodePriorities.containsKey(current)) { continue; } nodePriorities.put(current, ++priorityCounter); List<DiGraphNode<Node, Branch>> successors = cfg.getDirectedSuccNodes(current); for (DiGraphNode<Node, Branch> candidate : successors) { worklist.add(candidate); } } } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { astPosition.put(n, astPositionCounter++); switch (n.getType()) { case Token.FUNCTION: if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) { exceptionHandler.push(n); return true; } return false; case Token.TRY: exceptionHandler.push(n); return true; } /* * We are going to stop the traversal depending on what the node's parent * is. * * We are only interested in adding edges between nodes that change control * flow. The most obvious ones are loops and IF-ELSE's. A statement * transfers control to its next sibling. * * In case of an expression tree, there is no control flow within the tree * even when there are short circuited

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> operators and conditionals. When we * are doing data flow analysis, we will simply synthesize lattices up the * expression tree by finding the meet at each expression node. * * For example: within a Token.SWITCH, the expression in question does not * change the control flow and need not to be considered. */ if (parent != null) { switch (parent.getType()) { case Token.FOR: // Only traverse the body of the for loop. return n == parent.getLastChild(); // Skip the conditions. case Token.IF: case Token.WHILE: case Token.WITH: return n != parent.getFirstChild(); case Token.DO: return n != parent.getFirstChild().getNext(); // Only traverse the body of the cases case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.LABEL: return n != parent.getFirstChild(); case Token.FUNCTION: return n == parent.getFirstChild().getNext().getNext(); case Token.CONTINUE: case Token.BREAK: case Token.EXPR_RESULT: case Token.VAR: case Token.RETURN: case Token.THROW: return false; case Token.TRY: /* Just before we are about to visit the second child of the TRY node, * we know that we will be visiting either the CATCH or the FINALLY. * In other words, we know that the post order traversal of the TRY * block has been finished, no more exceptions can be caught by the * handler at this TRY block and should be taken out of the stack. */ if (n == parent.getFirstChild().getNext()) { Preconditions.checkState(exceptionHandler.peek() == parent); exceptionHandler.pop(); } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.IF: handleIf(n); return; case Token.WHILE: handleWhile(n); return; case Token.DO: handleDo(n); return; case Token.FOR: handleFor(n); return; case Token.SWITCH: handleSwitch(n); return; case Token.CASE: handleCase(n); return; case Token.DEFAULT: handleDefault(n); return; case Token.BLOCK: case Token.SCRIPT: handleStmtList(n); return; case Token.FUNCTION: handleFunction(n); return; case Token.EXPR_RESULT: handleExpr(n); return; case Token.THROW: handleThrow(n); return; case Token.TRY: handleTry(n); return; case Token.CATCH: handleCatch(n); return; case Token.BREAK: handleBreak(n); return; case Token.CONTINUE: handleContinue(n); return;

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case Token.RETURN: handleReturn(n); return; case Token.WITH: handleWith(n); return; case Token.LABEL: return; default: handleStmt(n); return; } } private void handleIf(Node node) { Node thenBlock = node.getFirstChild().getNext(); Node elseBlock = thenBlock.getNext(); createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock)); if (elseBlock == null) { createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); // not taken branch } else { createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock)); } connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleWhile(Node node) { // Control goes to the first statement if the condition evaluates to true. createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild().getNext())); // Control goes to the follow() if the condition evaluates to false. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleDo(Node node) { // The first edge can be the initial iteration as well as the iterations // after. createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild())); // The edge that leaves the do loop if the condition fails. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleFor(Node forNode) { if (forNode.getChildCount() == 4) { // We have for (init; cond; iter) { body } Node init = forNode.getFirstChild(); Node cond = init.getNext(); Node iter = cond.getNext(); Node body = iter.getNext(); // After initialization, we transfer to the FOR which is in charge of // checking the condition (for the first time). createEdge(init, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); // The end of the body will have a unconditional branch to our iter // (handled by calling computeFollowNode of the last instruction of the // body. Our iter will jump to the forNode again to another condition // check. createEdge(iter, Branch.UNCOND, forNode); connectToPossibleExceptionHandler(init, init); connectToPossibleExceptionHandler(forNode, cond); connectToPossibleExceptionHandler(iter,

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> iter); } else { // We have for (item in collection) { body } Node item = forNode.getFirstChild(); Node collection = item.getNext(); Node body = collection.getNext(); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); connectToPossibleExceptionHandler(forNode, collection); } } private void handleSwitch(Node node) { // Transfer to the first non-DEFAULT CASE. if there are none, transfer // to the DEFAULT or the EMPTY node. Node next = getNextSiblingOfType( node.getFirstChild().getNext(), Token.CASE, Token.EMPTY); if (next != null) { // Has at least one CASE or EMPTY createEdge(node, Branch.UNCOND, next); } else { // Has no CASE but possibly a DEFAULT if (node.getFirstChild().getNext() != null) { createEdge(node, Branch.UNCOND, node.getFirstChild().getNext()); } else { // No CASE, no DEFAULT createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleCase(Node node) { // Case is a bit tricky....First it goes into the body if condition is true. createEdge(node, Branch.ON_TRUE, node.getFirstChild().getNext()); // Look for the next CASE, skipping over DEFAULT. Node next = getNextSiblingOfType(node.getNext(), Token.CASE); if (next != null) { // Found a CASE Preconditions.checkState(next.getType() == Token.CASE); createEdge(node, Branch.ON_FALSE, next); } else { // No more CASE found, go back and search for a DEFAULT. Node parent = node.getParent(); Node deflt = getNextSiblingOfType( parent.getFirstChild().getNext(), Token.DEFAULT); if (deflt != null) { // Has a DEFAULT createEdge(node, Branch.ON_FALSE, deflt); } else { // No DEFAULT found, go to the follow of the SWITCH. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleDefault(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleWith(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getLastChild()); connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleStmt

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>List(Node node) { Node parent = node.getParent(); // Special case, don't add a block of empty CATCH block to the graph. if (node.getType() == Token.BLOCK && parent != null && parent.getType() == Token.TRY && NodeUtil.getCatchBlock(parent) == node && !NodeUtil.hasCatchHandler(node)) { return; } // A block transfer control to its first child if it is not empty. Node child = node.getFirstChild(); // Function declarations are skipped since control doesn't go into that // function (unless it is called) while (child != null && child.getType() == Token.FUNCTION) { child = child.getNext(); } if (child != null) { createEdge(node, Branch.UNCOND, computeFallThrough(child)); } else { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } // Synthetic blocks if (parent != null) { switch (parent.getType()) { case Token.DEFAULT: case Token.CASE: case Token.TRY: break; default: if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) { createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this)); } break; } } } private void handleFunction(Node node) { // A block transfer control to its first child if it is not empty. Preconditions.checkState(node.getChildCount() >= 3); createEdge(node, Branch.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext())); Preconditions.checkState(exceptionHandler.peek() == node); exceptionHandler.pop(); } private void handleExpr(Node node) { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } private void handleThrow(Node node) { connectToPossibleExceptionHandler(node, node); } private void handleTry(Node node) { createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleCatch(Node node) { createEdge(node, Branch.UNCOND, node.getLastChild()); } private void handleBreak(Node node) { String label = null; // See if it is a break with label. if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; Node parent = node.getParent(); /* * Continuously look up the ancestor tree for the BREAK target or the target * with the corresponding label and connect to it. If along the path we * discover a FINALLY, we will connect the BREAK to that FINALLY. From then * on, we will just record the control flow changes in the finallyMap. This * is due to the fact that we need to connect any node that leaves its

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> own * FINALLY block to the outer FINALLY or the BREAK's target but those nodes * are not known yet due to the way we traverse the nodes. */ for (cur = node, lastJump = node; !isBreakTarget(cur, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFallThrough( cur.getLastChild())); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find break target."); } if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this)); } else { finallyMap.put(lastJump, computeFollowNode(cur, this)); } } private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } } private void handleReturn(Node node) { Node lastJump = null; for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) { Node curHandler = iter.next(); if (NodeUtil.isFunction(curHandler)) { break; } if (NodeUtil.hasFinally(curHandler)) { if (lastJump == null) { createEdge(node, Branch.UNCOND, curHandler.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(curHandler.getLastChild())); } lastJump = curHandler; } } if (node.hasChildren()) { connectToPossibleExceptionHandler(node, node.getFirstChild()); } if (last

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Jump == null) { createEdge(node, Branch.UNCOND, null); } else { finallyMap.put(lastJump, null); } } private void handleStmt(Node node) { // Simply transfer to the next line. createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) { return computeFollowNode(node, node, cfa); } static Node computeFollowNode(Node node) { return computeFollowNode(node, node, null); } /** * Computes the follow() node of a given node and its parent. There is a side * effect when calling this function. If this function computed an edge that * exists a FINALLY, it'll attempt to connect the fromNode to the outer * FINALLY according to the finallyMap. * * @param fromNode The original source node since {@code node} is changed * during recursion. * @param node The node that follow() should compute. */ private static Node computeFollowNode( Node fromNode, Node node, ControlFlowAnalysis cfa) { /* * This is the case where: * * 1. Parent is null implies that we are transferring control to the end of * the script. * * 2. Parent is a function implies that we are transferring control back to * the caller of the function. * * 3. If the node is a return statement, we should also transfer control * back to the caller of the function. * * 4. If the node is root then we have reached the end of what we have been * asked to traverse. * * In all cases we should transfer control to a "symbolic return" node. * This will make life easier for DFAs. */ Node parent = node.getParent(); if (parent == null || parent.getType() == Token.FUNCTION || (cfa != null && node == cfa.root)) { return null; } // If we are just before a IF/WHILE/DO/FOR: switch (parent.getType()) { // The follow() of any of the path from IF would be what follows IF. case Token.IF: return computeFollowNode(fromNode, parent, cfa); case Token.CASE: case Token.DEFAULT: // After the body of a CASE, the control goes to the body of the next // case, without having to go to the case condition. if (parent.getNext() != null) { if (parent.getNext().getType() == Token.CASE) { return parent.getNext().getFirstChild().getNext(); } else if (parent.getNext().getType() == Token.DEFAULT) { return parent.getNext().getFirstChild(); } else { Preconditions.checkState(false, "Not reachable"); } } else { return computeFollowNode

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(fromNode, parent, cfa); } break; case Token.FOR: if (NodeUtil.isForIn(parent)) { return parent; } else { return parent.getFirstChild().getNext().getNext(); } case Token.WHILE: case Token.DO: return parent; case Token.TRY: // If we are coming out of the TRY block... if (parent.getFirstChild() == node) { if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(parent.getLastChild()); } else { // and have no FINALLY. return computeFollowNode(fromNode, parent, cfa); } // CATCH block. } else if (NodeUtil.getCatchBlock(parent) == node){ if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(node.getNext()); } else { return computeFollowNode(fromNode, parent, cfa); } // If we are coming out of the FINALLY block... } else if (parent.getLastChild() == node){ if (cfa != null) { for (Node finallyNode : cfa.finallyMap.get(parent)) { cfa.createEdge(fromNode, Branch.UNCOND, finallyNode); } } return computeFollowNode(fromNode, parent, cfa); } } // Now that we are done with the special cases follow should be its // immediate sibling, unless its sibling is a function Node nextSibling = node.getNext(); // Skip function declarations because control doesn't get pass into it. while (nextSibling != null && nextSibling.getType() == Token.FUNCTION) { nextSibling = nextSibling.getNext(); } if (nextSibling != null) { return computeFallThrough(nextSibling); } else { // If there are no more siblings, control is transfered up the AST. return computeFollowNode(fromNode, parent, cfa); } } /** * Computes the destination node of n when we want to fallthough into the * subtree of n. We don't always create a CFG edge into n itself because of * DOs and FORs. */ static Node computeFallThrough(Node n) { switch (n.getType()) { case Token.DO: return computeFallThrough(n.getFirstChild()); case Token.FOR: if (NodeUtil.isForIn(n)) { return n; } return computeFallThrough(n.getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } } /** * Connects the two nodes in the control flow graph. * * @param fromNode Source. * @param toNode Destination. */ private void createEdge(Node fromNode, ControlFlowGraph.Branch branch, Node toNode) { cfg.createNode(

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>fromNode); cfg.createNode(toNode); cfg.connectIfNotFound(fromNode, branch, toNode); } /** * Connects cfgNode to the proper CATCH block if target subtree might throw * an exception. If there are FINALLY blocks reached before a CATCH, it will * make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (NodeUtil.isFunction(handler)) { return; } Preconditions.checkState(handler.getType() == Token.TRY); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } } /** * Get the next sibling (including itself) of one of the given types. */ private static Node getNextSiblingOfType(Node first, int ... types) { for (Node c = first; c != null; c = c.getNext()) { for (int type : types) { if (c.getType() == type) { return c; } } } return null; } /** * Checks if target is actually the break target of labeled continue. The * label can be null if it is an unlabeled break. */ public static boolean isBreakTarget(Node target, String label) { return isBreakStructure(target, label != null) && matchLabel(target.getParent(), label); } /** * Checks if target is actually the continue target of labeled continue. The * label can be null if it is an unlabeled continue. */ private static boolean isContinueTarget( Node target, Node parent, String label) { return isContinueStructure(target) && matchLabel(parent, label); } /** * Check if label is actually referencing the target control structure. If * label is null, it always returns true. */ private static boolean matchLabel(Node target, String label) { if (label == null) { return true; } while (target.getType() == Token.LABEL) { if (target.getFirstChild().getString().equals(label)) { return true; } target = target.getParent(); } return false; } /** * Determines if the subtree might throw an exception. */

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> public static boolean mayThrowException(Node n) { switch (n.getType()) { case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.THROW: case Token.NEW: case Token.ASSIGN: case Token.INC: case Token.DEC: case Token.INSTANCEOF: return true; case Token.FUNCTION: return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) { return true; } } return false; } /** * Determines whether the given node can be terminated with a BREAK node. */ static boolean isBreakStructure(Node n, boolean labeled) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.SWITCH: return true; case Token.BLOCK: case Token.IF: case Token.TRY: return labeled; default: return false; } } /** * Determines whether the given node can be advanced with a CONTINUE node. */ static boolean isContinueStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * Get the TRY block with a CATCH that would be run if n throws an exception. * @return The CATCH node or null if it there isn't a CATCH before the * the function terminates. */ static Node getExceptionHandler(Node n) { for (Node cur = n; cur.getType() != Token.SCRIPT && cur.getType() != Token.FUNCTION; cur = cur.getParent()) { Node catchNode = getCatchHandlerForBlock(cur); if (catchNode != null) { return catchNode; } } return null; } /** * Locate the catch BLOCK given the first block in a TRY. * @return The CATCH node or null there is no catch handler. */ static Node getCatchHandlerForBlock(Node block) { if (block.getType() == Token.BLOCK && block.getParent().getType() == Token.TRY && block.getParent().getFirstChild() == block) { for (Node s = block.getNext(); s != null; s = s.getNext()) { if (NodeUtil.hasCatchHandler(s)) { return s.getFirstChild(); } } } return null; } /** * A {@link ControlFlowGraph} which provides a node comparator based on the * pre-order traversal of the AST. */ private static class AstControlFlowGraph extends ControlFlowGraph<Node> { private final Map<DiGraphNode<Node, Branch>, Integer> priorities; /** * Constructor.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * @param entry The entry node. * @param priorities The map from nodes to position in the AST (to be * filled by the {@link ControlFlowAnalysis#shouldTraverse}). */ private AstControlFlowGraph(Node entry, Map<DiGraphNode<Node, Branch>, Integer> priorities, boolean edgeAnnotations) { super(entry, true /* node annotations */, edgeAnnotations); this.priorities = priorities; } @Override /** * Returns a node comparator based on the pre-order traversal of the AST. * @param isForward x 'before' y in the pre-order traversal implies * x 'less than' y (if true) and x 'greater than' y (if false). */ public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator( boolean isForward) { if (isForward) { return new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) { return getPosition(n1) - getPosition(n2); } }; } else { return new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) { return getPosition(n2) - getPosition(n1); } }; } } /** * Gets the pre-order traversal position of the given node. * @return An arbitrary counter used for comparing positions. */ private int getPosition(DiGraphNode<Node, Branch> n) { Integer priority = priorities.get(n); Preconditions.checkNotNull(priority); return priority; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> Unlike most types in our type system, the collection of types returned * will not be collapsed. This means that if a type is defined on * {@code Object} and on {@code Array}, this method must return * {@code [Object, Array]}. It would not be correct to collapse them to * {@code [Object]}. */ public Iterable<ObjectType> getEachReferenceTypeWithProperty( String propertyName) { if (eachRefTypeIndexedByProperty.containsKey(propertyName)) { return eachRefTypeIndexedByProperty.get(propertyName).values(); } else { return ImmutableList.of(); } } /** * Increments the current generation. Clients must call this in order to * move to the next generation of type resolution, allowing types to attempt * resolution again. */ public void incrementGeneration() { for (NamedType type : resolvedNamedTypes.values()) { type.clearResolved(); } unresolvedNamedTypes.putAll(resolvedNamedTypes); resolvedNamedTypes.clear(); } boolean isLastGeneration() { return lastGeneration; } /** * Sets whether this is the last generation. In the last generation, * {@link NamedType} warns about unresolved types. */ public void setLastGeneration(boolean lastGeneration) { this.lastGeneration = lastGeneration; } /** * Tells the type system that {@code type} implements interface {@code * InterfaceInstance}. * {@code inter} must be an ObjectType for the instance of the interface as it * could be a named type and not yet have the constructor. */ void registerTypeImplementingInterface( FunctionType type, ObjectType interfaceInstance) { interfaceToImplementors.put(interfaceInstance.getReferenceName(), type); } /** * Returns a collection of types that directly implement {@code * interfaceInstance}. Subtypes of implementing types are not guaranteed to * be returned. {@code interfaceInstance} must be an ObjectType for the * instance of the interface. */ public Collection<FunctionType> getDirectImplementors( ObjectType interfaceInstance) { return interfaceToImplementors.get(interfaceInstance.getReferenceName()); } /** * Records declared global type names. This makes resolution faster * and more robust in the common case. * * @param name The name of the type to be recorded. * @param t The actual type being associated with the name. * @return True if this name is not already defined, false otherwise. */ public boolean declareType(String name, JSType t) { if (namesToTypes.containsKey(name)) { return false; } register(t, name); return true; } /** * Overrides a declared global type name. Throws an exception if this * type name hasn't been declared yet. */ public void overwriteDeclaredType(String name, JSType t) { Preconditions.checkState(namesToTypes.containsKey(name)); register(t, name); } /** * Records a forward

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>-declared type name. We will not emit errors if this * type name never resolves to anything. */ public void forwardDeclareType(String name) { forwardDeclaredTypes.add(name); } /** * Whether this is a forward-declared type name. */ public boolean isForwardDeclaredType(String name) { return forwardDeclaredTypes.contains(name); } /** Determines whether the given JS package exists. */ public boolean hasNamespace(String name) { return namespaces.contains(name); } /** * Looks up a type by name. * * @param jsTypeName The name string. * @return the corresponding JSType object or {@code null} it cannot be found */ public JSType getType(String jsTypeName) { // TODO(user): Push every local type name out of namesToTypes so that // NamedType#resolve is correct. if (jsTypeName.equals(templateTypeName)) { return templateType; } return namesToTypes.get(jsTypeName); } public JSType getNativeType(JSTypeNative typeId) { return nativeTypes[typeId.ordinal()]; } public ObjectType getNativeObjectType(JSTypeNative typeId) { return (ObjectType) getNativeType(typeId); } public FunctionType getNativeFunctionType(JSTypeNative typeId) { return (FunctionType) getNativeType(typeId); } /** * Try to resolve a type name, but forgive the user and don't emit * a warning if this doesn't resolve. */ public JSType getForgivingType(StaticScope<JSType> scope, String jsTypeName, String sourceName, int lineno, int charno) { JSType type = getType( scope, jsTypeName, sourceName, lineno, charno); type.forgiveUnknownNames(); return type; } /** * Looks up a type by name. To allow for forward references to types, an * unrecognized string has to be bound to a NamedType object that will be * resolved later. * * @param scope A scope for doing type name resolution. * @param jsTypeName The name string. * @param sourceName The name of the source file where this reference appears. * @param lineno The line number of the reference. * @return a NamedType if the string argument is not one of the known types, * otherwise the corresponding JSType object. */ public JSType getType(StaticScope<JSType> scope, String jsTypeName, String sourceName, int lineno, int charno) { JSType type = getType(jsTypeName); if (type == null) { // TODO(user): Each instance should support named type creation using // interning. NamedType namedType = new NamedType(this, jsTypeName, sourceName, lineno, charno); unresolvedNamedTypes.put(scope, namedType); type = namedType; } return type; } /** * Resolve all the un

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>resolved types in the given scope. */ public void resolveTypesInScope(StaticScope<JSType> scope) { for (NamedType type : unresolvedNamedTypes.get(scope)) { type.resolve(reporter, scope); } resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope)); if (scope != null && scope.getParentScope() == null) { // By default, the global "this" type is just an anonymous object. // If the user has defined a Window type, make the Window the // implicit prototype of "this". PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType( JSTypeNative.GLOBAL_THIS); JSType windowType = getType("Window"); if (globalThis.isUnknownType()) { ObjectType windowObjType = ObjectType.cast(windowType); if (windowObjType != null) { globalThis.setImplicitPrototype(windowObjType); } else { globalThis.setImplicitPrototype( getNativeObjectType(JSTypeNative.OBJECT_TYPE)); } } } } /** * Creates a type representing optional values of the given type. * @return the union of the type and the void type */ public JSType createOptionalType(JSType type) { if (type instanceof UnknownType || type.isAllType()) { return type; } else { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE)); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createDefaultObjectUnion(JSType type) { return shouldTolerateUndefinedValues() ? createOptionalNullableType(type) : createNullableType(type); } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a nullabel and undefine-able value of the given type. * @return The union of the type and null and undefined. */ public JSType createOptionalNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE), getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a union type whose variants are the arguments. */ public JSType createUnionType(JSType... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSType type : variants) { builder.addAlternate(type); } return builder.build(); } /** * Creates a union type whose variants are the builtin types specified * by the arguments. */ public JSType createUnionType(JSTypeNative... variants) { UnionTypeBuilder builder = new UnionTypeBuilder

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>, objectType, parameterType); } /** * Creates a named type. */ @VisibleForTesting public JSType createNamedType(String reference, String sourceName, int lineno, int charno) { return new NamedType(this, reference, sourceName, lineno, charno); } /** * Identifies the name of a typedef or enum before we actually declare it. */ public void identifyNonNullableName(String name) { Preconditions.checkNotNull(name); nonNullableTypeNames.add(name); } /** * Creates a JSType from the nodes representing a type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ public JSType createFromTypeNodes(Node n, String sourceName, StaticScope<JSType> scope) { return createFromTypeNodes(n, sourceName, scope, false); } /** * Creates a JSType from the nodes representing a type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. * @param forgiving Whether we should be forgiving about type names * that we can't find. */ public JSType createFromTypeNodes(Node n, String sourceName, StaticScope<JSType> scope, boolean forgiving) { if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) { // If the type expression doesn't contain any names, just // resolve it anyway. boolean hasNames = hasTypeName(n); if (hasNames) { return new UnresolvedTypeExpression(this, n, sourceName, forgiving); } } return createFromTypeNodesInternal(n, sourceName, scope, forgiving); } private boolean hasTypeName(Node n) { if (n.getType() == Token.STRING) { return true; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (hasTypeName(child)) { return true; } } return false; } /** @see #createFromTypeNodes(Node, String, StaticScope, boolean) */ private JSType createFromTypeNodesInternal(Node n, String sourceName, StaticScope<JSType> scope, boolean forgiving) { switch (n.getType()) { case Token.LC: // Record type. return createRecordTypeFromNodes( n.getFirstChild(), sourceName, scope); case Token.BANG: // Not nullable return createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope, forgiving) .restrictByNotNullOrUndefined(); case Token.QMARK: // Nullable or unknown Node firstChild = n.getFirstChild(); if (firstChild == null) { return getNativeType(UNKNOWN_TYPE); }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> return createDefaultObjectUnion( createFromTypeNodesInternal( firstChild, sourceName, scope, forgiving)); case Token.EQUALS: // Optional return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope, false)); case Token.ELLIPSIS: // Var args return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope, false)); case Token.STAR: // The AllType return getNativeType(ALL_TYPE); case Token.LB: // Array type // TODO(nicksantos): Enforce membership restrictions on the Array. return getNativeType(ARRAY_TYPE); case Token.PIPE: // Union type UnionTypeBuilder builder = new UnionTypeBuilder(this); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { builder.addAlternate( createFromTypeNodesInternal(child, sourceName, scope, false)); } return builder.build(); case Token.EMPTY: // When the return value of a function is not specified return getNativeType(UNKNOWN_TYPE); case Token.VOID: // Only allowed in the return value of a function. return getNativeType(VOID_TYPE); case Token.STRING: JSType namedType = getType(scope, n.getString(), sourceName, n.getLineno(), n.getCharno()); if (forgiving) { namedType.forgiveUnknownNames(); } if (resolveMode != ResolveMode.LAZY_NAMES) { namedType = namedType.resolveInternal(reporter, scope); } if ((namedType instanceof ObjectType) && !(nonNullableTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); if (typeList != null && ("Array".equals(n.getString()) || "Object".equals(n.getString()))) { JSType parameterType = createFromTypeNodesInternal( typeList.getLastChild(), sourceName, scope, false); namedType = new ParameterizedType( this, (ObjectType) namedType, parameterType); if (typeList.hasMoreThanOneChild()) { JSType indexType = createFromTypeNodesInternal( typeList.getFirstChild(), sourceName, scope, false); namedType = new IndexedType( this, (ObjectType) namedType, indexType); } } return createDefaultObjectUnion(namedType); } else { return namedType; } case Token.FUNCTION: ObjectType thisType = null; Node current = n.getFirstChild(); if (current.getType() == Token.THIS) { Node thisNode = current.getFirstChild(); thisType = ObjectType.cast( createFromTypeNodesInternal( thisNode, sourceName, scope, false) .restrictByNotNullOrUndefined()); if (thisType == null) { reporter.warning( ScriptRuntime.getMessage0("msg.js

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>doc.function.thisnotobject"), sourceName, thisNode.getLineno(), "", thisNode.getCharno()); } current = current.getNext(); } FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this); if (current.getType() == Token.LP) { Node args = current.getFirstChild(); for (Node arg = current.getFirstChild(); arg != null; arg = arg.getNext()) { if (arg.getType() == Token.ELLIPSIS) { if (arg.getChildCount() == 0) { paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE)); } else { paramBuilder.addVarArgs( createFromTypeNodesInternal( arg.getFirstChild(), sourceName, scope, false)); } } else { JSType type = createFromTypeNodesInternal( arg, sourceName, scope, false); if (arg.getType() == Token.EQUALS) { boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess) { reporter.warning( ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"), sourceName, arg.getLineno(), "", arg.getCharno()); } } else { paramBuilder.addRequiredParams(type); } } } current = current.getNext(); } JSType returnType = createFromTypeNodesInternal(current, sourceName, scope, false); return new FunctionBuilder(this) .withParams(paramBuilder) .withReturnType(returnType) .withTypeOfThis(thisType) .build(); } throw new IllegalStateException( "Unexpected node in type expression: " + n.toString()); } /** * Creates a RecordType from the nodes representing said record type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticScope<JSType> scope) { RecordTypeBuilder builder = new RecordTypeBuilder(this); // For each of the fields in the record type. for (Node fieldTypeNode = n.getFirstChild(); fieldTypeNode != null; fieldTypeNode = fieldTypeNode.getNext()) { // Get the property's name. Node fieldNameNode = fieldTypeNode; boolean hasType = false; if (fieldTypeNode.getType() == Token.COLON) { fieldNameNode = fieldTypeNode.getFirstChild(); hasType = true; } String fieldName = fieldNameNode.getString(); // TODO(user): Move this into the lexer/parser. // Remove the string literal characters around a field name, // if any. if (fieldName.startsWith("'") || fieldName.startsWith("\"")) { fieldName = fieldName.substring(1, fieldName.length() - 1); } // Get the property's type. JSType fieldType = null; if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> * Gets a comparator for the nodes. The default implementation returns * {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}. * @param isForward Whether the comparator sorts the nodes in the direction of * the flow. * @return a comparator or null (in particular, if not overriden) */ public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator( boolean isForward) { return null; } /** * The edge object for the control flow graph. */ public static enum Branch { /** Edge is taken if the condition is true. */ ON_TRUE, /** Edge is taken if the condition is false. */ ON_FALSE, /** Unconditional branch. */ UNCOND, /** Exception related. */ ON_EX, /** Possible folded-away template */ SYN_BLOCK; public boolean isConditional() { return this == ON_TRUE || this == ON_FALSE; } } /** * Abstract callback to visit a control flow graph node without going into * subtrees of the node that is also represented by another control flow graph * node. * * <p>For example, traversing an IF node as root will visit the two subtree * pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and * {@link ControlFlowGraph.Branch#ON_FALSE} edge. */ public abstract static class AbstractCfgNodeTraversalCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (parent == null) { return true; } return !isEnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: case Token.FINALLY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // is bleed into the local scope and parameters has been assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body represent by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token.DO: case Token.IF: // Theses control structure is represented by its node that holds the // condition. Each of them is a branch node based on its condition. return NodeUtil.getConditionExpression(parent) != n; case Token.FOR: // The FOR(;;) node differs from other control structure in that // it has a initialization and a increment statement. Those //

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> two statements have its corresponding CFG nodes to represent them. // The FOR node represents the condition check for each iteration. // That way the following: // for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to // var x = 0; while(x<10) { x++; } if (NodeUtil.isForIn(parent)) { return n == parent.getLastChild(); } else { return NodeUtil.getConditionExpression(parent) != n; } case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.WITH: return n != parent.getFirstChild(); default: return false; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Norris Boyd * Roger Lawrence * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino; public class FunctionNode extends ScriptOrFnNode { private static final long serialVersionUID = 1L; public FunctionNode(String name) { super(Token.FUNCTION); functionName = name; } public FunctionNode(String name, int lineno, int charno) { super(Token.FUNCTION, lineno, charno); functionName = name; } public String getFunctionName() { return functionName; } public boolean requiresActivation() { return itsNeedsActivation; } public boolean getIgnoreDynamicScope() { return itsIgnoreDynamicScope; } /** * There are three types of functions that can be defined. The first * is a function statement. This is a function appearing as a top-level * statement (i.e., not nested inside some other statement) in either a * script or a function. * * The second is a function expression, which is a function appearing in * an expression except for the third type, which is... * * The third type is a function expression where the expression is the * top-level expression in an expression statement. * * The three

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> void setEnumParameterType(JSTypeExpression type) { setType(type, TYPEFIELD_ENUM); } void setTypedefType(JSTypeExpression type) { setType(type, TYPEFIELD_TYPEDEF); } private void setType(JSTypeExpression type, int mask) { if ((bitset & MASK_TYPEFIELD) != 0) { throw new IllegalStateException( "API tried to add two incompatible type tags. " + "This should have been blocked and emitted a warning."); } this.bitset = (bitset & MASK_FLAGS) | mask; this.type = type; } /** * Returns the list of thrown types. */ public List<JSTypeExpression> getThrownTypes() { if (info == null || info.thrownTypes == null) { return ImmutableList.of(); } return Collections.unmodifiableList(info.thrownTypes); } /** * Returns whether a type, specified using the {@code @type} annotation, is * present on this JSDoc. */ public boolean hasType() { return hasType(TYPEFIELD_TYPE); } /** * Returns whether an enum parameter type, specified using the {@code @enum} * annotation, is present on this JSDoc. */ public boolean hasEnumParameterType() { return hasType(TYPEFIELD_ENUM); } /** * Returns whether a typedef parameter type, specified using the * {@code @typedef} annotation, is present on this JSDoc. */ public boolean hasTypedefType() { return hasType(TYPEFIELD_TYPEDEF); } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @return} * annotation. */ public boolean hasReturnType() { return hasType(TYPEFIELD_RETURN); } private boolean hasType(int mask) { return (bitset & MASK_TYPEFIELD) == mask; } /** * Gets the type specified by the {@code @type} annotation. */ public JSTypeExpression getType() { return getType(TYPEFIELD_TYPE); } /** * Gets the return type specified by the {@code @return} annotation. */ public JSTypeExpression getReturnType() { return getType(TYPEFIELD_RETURN); } /** * Gets the enum parameter type specified by the {@code @enum} annotation. */ public JSTypeExpression getEnumParameterType() { return getType(TYPEFIELD_ENUM); } /** * Gets the typedef type specified by the {@code @type} annotation. */ public JSTypeExpression getTypedefType() { return getType(TYPEFIELD_TYPEDEF); } private JSTypeExpression getType(int typefield) { if ((MASK_TYPEFIELD & bitset) == typefield) { return type; } else { return null; } } /** * Gets the type specified by the {@code @this} annotation. */ public JSTypeExpression getThisType() {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> parent; /** * Full chain of ASSIGN ancestors */ final List<Node> assignAncestors = Lists.newArrayList(); /** * The last ancestor */ final Node lastAncestor; /** * Data structure for information about a removable assignment. * * @param nameNode The LHS * @param assignNode The parent ASSIGN node * @param traversal Access to further levels, assumed to start at 1 */ public RemovableAssignment(Node nameNode, Node assignNode, NodeTraversal traversal) { this.node = nameNode; this.parent = assignNode; Node ancestor = assignNode; do { ancestor = ancestor.getParent(); assignAncestors.add(ancestor); } while (ancestor.getType() == Token.ASSIGN && ancestor.getFirstChild().isQualifiedName()); lastAncestor = ancestor.getParent(); } /** * Remove this node. */ public void remove() { Node rhs = node.getNext(); Node last = parent; for (Node ancestor : assignAncestors) { if (NodeUtil.isExpressionNode(ancestor)) { lastAncestor.removeChild(ancestor); } else { rhs.detachFromParent(); ancestor.replaceChild(last, rhs); } last = ancestor; } compiler.reportCodeChange(); } } /** * Identifies all assignments of the abstract method to a variable. */ private class FindAbstractMethods extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.ASSIGN) { Node nameNode = n.getFirstChild(); Node valueNode = n.getLastChild(); if (nameNode.isQualifiedName() && valueNode.isQualifiedName() && ABSTRACT_METHOD_NAME.equals(valueNode.getQualifiedName())) { abstractMethodAssignmentNodes.add(new RemovableAssignment( n.getFirstChild(), n, t)); } } } } /** * Identifies all assertion calls. */ private class FindAssertionCalls extends AbstractPostOrderCallback { Set<String> assertionNames = Sets.newHashSet(); FindAssertionCalls() { for (AssertionFunctionSpec spec : compiler.getCodingConvention().getAssertionFunctions()) { assertionNames.add(spec.getFunctionName()); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.CALL) { String fnName = n.getFirstChild().getQualifiedName(); if (assertionNames.contains(fnName)) { assertionCalls.add(n); } } } } /** * Creates a Closure code remover. * * @param compiler The AbstractCompiler * @param removeAbstractMethods Remove declarations of abstract methods. * @param removeAssertionCalls Remove calls to goog.assert functions. */ ClosureCodeRemoval(AbstractCompiler compiler, boolean removeAbstractMethods, boolean removeAssertionCalls) {

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>. */ static final String DELEGATE_PROXY_SUFFIX = ObjectType.createDelegateSuffix("Proxy"); private static final String LEGACY_TYPEDEF = "goog.typedef"; static final DiagnosticType MALFORMED_TYPEDEF = DiagnosticType.warning( "JSC_MALFORMED_TYPEDEF", "Typedef for {0} does not have any type information"); static final DiagnosticType ENUM_INITIALIZER = DiagnosticType.warning( "JSC_ENUM_INITIALIZER_NOT_ENUM", "enum initializer must be an object literal or an enum"); static final DiagnosticType CTOR_INITIALIZER = DiagnosticType.warning( "JSC_CTOR_INITIALIZER_NOT_CTOR", "Constructor {0} must be initialized at declaration"); static final DiagnosticType IFACE_INITIALIZER = DiagnosticType.warning( "JSC_IFACE_INITIALIZER_NOT_IFACE", "Interface {0} must be initialized at declaration"); static final DiagnosticType CONSTRUCTOR_EXPECTED = DiagnosticType.warning( "JSC_REFLECT_CONSTRUCTOR_EXPECTED", "Constructor expected as first argument"); static final DiagnosticType UNKNOWN_LENDS = DiagnosticType.warning( "JSC_UNKNOWN_LENDS", "Variable {0} not declared before @lends annotation."); static final DiagnosticType LENDS_ON_NON_OBJECT = DiagnosticType.warning( "JSC_LENDS_ON_NON_OBJECT", "May only lend properties to object types. {0} has type {1}."); private final AbstractCompiler compiler; private final ErrorReporter typeParsingErrorReporter; private final TypeValidator validator; private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private List<ObjectType> delegateProxyPrototypes = Lists.newArrayList(); /** * Defer attachment of types to nodes until all type names * have been resolved. Then, we can resolve the type and attach it. */ private class DeferredSetType { final Node node; final JSType type; DeferredSetType(Node node, JSType type) { Preconditions.checkNotNull(node); Preconditions.checkNotNull(type); this.node = node; this.type = type; // Other parts of this pass may read off the node. // (like when we set the LHS of an assign with a typed RHS function.) node.setJSType(type); } void resolve(Scope scope) { node.setJSType(type.resolve(typeParsingErrorReporter, scope)); } } TypedScopeCreator(AbstractCompiler compiler) { this(compiler, compiler.getCodingConvention()); } TypedScopeCreator(AbstractCompiler compiler, CodingConvention codingConvention) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.codingConvention = codingConvention; this.typeRegistry = compiler.getTypeRegistry(); this.typeParsingErrorReporter = typeRegistry.getErrorReporter(); } /**

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>TYPE); declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE); declareNativeValueType(s, "undefined", VOID_TYPE); // The typedef construct needs the any type, so that it can be assigned // to anything. This is kind of a hack, and an artifact of the typedef // syntax we've chosen. declareNativeValueType(s, LEGACY_TYPEDEF, NO_TYPE); // ActiveXObject is unqiuely special, because it can be used to construct // any type (the type that it creates is related to the arguments you // pass to it). declareNativeValueType(s, "ActiveXObject", NO_OBJECT_TYPE); return s; } private void declareNativeFunctionType(Scope scope, JSTypeNative tId) { FunctionType t = typeRegistry.getNativeFunctionType(tId); declareNativeType(scope, t.getInstanceType().getReferenceName(), t); declareNativeType( scope, t.getPrototype().getReferenceName(), t.getPrototype()); } private void declareNativeValueType(Scope scope, String name, JSTypeNative tId) { declareNativeType(scope, name, typeRegistry.getNativeType(tId)); } private void declareNativeType(Scope scope, String name, JSType t) { scope.declare(name, null, t, null, false); } private static class DiscoverEnumsAndTypedefs extends AbstractShallowStatementCallback { private final JSTypeRegistry registry; DiscoverEnumsAndTypedefs(JSTypeRegistry registry) { this.registry = registry; } @Override public void visit(NodeTraversal t, Node node, Node parent) { Node nameNode = null; switch (node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, child.getFirstChild(), NodeUtil.getInfoForNameNode(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.getType() == Token.ASSIGN) { identifyNameNode( firstChild.getFirstChild(), firstChild.getLastChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, null, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, Node valueNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info.hasTypedefType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } if (valueNode != null && LEGACY_TYPEDEF.equals(valueNode.getQualifiedName

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>())) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } /** * Given a node, determines whether that node names a prototype * property, and if so, returns the qualified name node representing * the owner of that property. Otherwise, returns null. */ private static Node getPrototypePropertyOwner(Node n) { if (n.getType() == Token.GETPROP) { Node firstChild = n.getFirstChild(); if (firstChild.getType() == Token.GETPROP && firstChild.getLastChild().getString().equals("prototype")) { Node maybeOwner = firstChild.getFirstChild(); if (maybeOwner.isQualifiedName()) { return maybeOwner; } } } return null; } private JSType getNativeType(JSTypeNative nativeType) { return typeRegistry.getNativeType(nativeType); } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback { /** * The scope that we're builidng. */ final Scope scope; private final List<DeferredSetType> deferredSetTypes = Lists.newArrayList(); /** * Functions that we found in the global scope and not in externs. */ private final List<Node> nonExternFunctions = Lists.newArrayList(); /** * Type-less stubs. * * If at the end of traversal, we still don't have types for these * stubs, then we should declare UNKNOWN types. */ private final List<StubDeclaration> stubDeclarations = Lists.newArrayList(); /** * The current source file that we're in. */ private String sourceName = null; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } void setDeferredType(Node node, JSType type) { deferredSetTypes.add(new DeferredSetType(node, type)); } void resolveTypes() { // Resolve types and attach them to nodes. for (DeferredSetType deferred : deferredSetTypes) { deferred.resolve(scope); } // Resolve types and attach them to scope slots. Iterator<Var> vars = scope.getVars(); while (vars.hasNext()) { vars.next().resolveType(typeParsingErrorReporter); } // Tell the type registry that any remaining types // are unknown. typeRegistry.resolveTypesInScope(scope); } @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (n.getType() == Token.FUNCTION || n.getType() == Token.SCRIPT) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild() || parent == scope.getRootNode(); } @Override public

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> void visit(NodeTraversal t, Node n, Node parent) { attachLiteralTypes(n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n, parent); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // VARs and ASSIGNs are handled in different branches of this // switch statement. if (parent.getType() != Token.ASSIGN && parent.getType() != Token.NAME) { defineDeclaredFunction(n, parent); } break; case Token.ASSIGN: // Handle constructor and enum definitions. defineNamedTypeAssign(n, parent); // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.getType() == Token.GETPROP && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n, parent); break; case Token.VAR: defineVar(n, parent); break; case Token.GETPROP: // Handle stubbed properties. if (parent.getType() == Token.EXPR_RESULT && n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } } private void attachLiteralTypes(Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.REF_SPECIAL: n.setJSType(getNativeType(UNKNOWN_TYPE)); break; case Token.OBJECTLIT: processObjectLit(n); break; // NOTE(nicksantos): If we ever support Array tuples, // we will need to put ARRAYLIT here as well. } } private void processObjectLit(Node objectLit) { JSDocInfo info = objectLit.getJSDocInfo(); if (info != null && info.getLendsName() != null) { String lendsName = info.getLendsName(); Var lendsVar = scope.getVar(lendsName); if (lendsVar == null)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { compiler.report( JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName)); } else { JSType type = lendsVar.getType(); if (type == null) { type = typeRegistry.getNativeType(UNKNOWN_TYPE); } if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) { compiler.report( JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT, lendsName, type.toString())); } else { objectLit.setJSType(type); } } } if (objectLit.getJSType() == null) { objectLit.setJSType(typeRegistry.createAnonymousObjectType()); } } /** * Returns the type specified in a JSDoc annotation near a GETPROP or NAME. * * Extracts type information from either the {@code @type} tag or from * the {@code @return} and {@code @param} tags. */ JSType getDeclaredTypeInAnnotation( NodeTraversal t, Node node, JSDocInfo info) { return getDeclaredTypeInAnnotation(t.getSourceName(), node, info); } JSType getDeclaredTypeInAnnotation(String sourceName, Node node, JSDocInfo info) { JSType jsType = null; Node objNode = node.getType() == Token.GETPROP ? node.getFirstChild() : null; if (info != null) { if (info.hasType()) { jsType = info.getType().evaluate(scope, typeRegistry); } else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = node.getQualifiedName(); // constructors are often handled separately. if (info.isConstructor() && typeRegistry.getType(fnName) != null) { return null; } FunctionTypeBuilder builder = new FunctionTypeBuilder( fnName, compiler, node, sourceName, scope) .inferTemplateTypeName(info) .inferReturnType(info) .inferParameterTypes(info) .inferInheritance(info); // Infer the context type. boolean searchedForThisType = false; if (objNode != null) { if (objNode.getType() == Token.GETPROP && objNode.getLastChild().getString().equals("prototype")) { builder.inferThisType(info, objNode.getFirstChild()); searchedForThisType = true; } else if (objNode.getType() == Token.THIS) { builder.inferThisType(info, objNode.getJSType()); searchedForThisType = true; } } if (!searchedForThisType) { builder.inferThisType(info, (Node) null); } jsType = builder.buildAndRegister(); } } return jsType; } /** * Asserts that it's ok to define this node's name. * The node

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> should have a source name and be of the specified type. */ void assertDefinitionNode(Node n, int type) { Preconditions.checkState(sourceName != null); Preconditions.checkState(n.getType() == type); } /** * Defines a catch parameter. */ void defineCatch(Node n, Node parent) { assertDefinitionNode(n, Token.CATCH); Node catchName = n.getFirstChild(); defineSlot(catchName, n, null); } /** * Defines a VAR initialization. */ void defineVar(Node n, Node parent) { assertDefinitionNode(n, Token.VAR); JSDocInfo info = n.getJSDocInfo(); if (n.hasMoreThanOneChild()) { if (info != null) { // multiple children compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF)); } for (Node name : n.children()) { defineName(name, n, parent, name.getJSDocInfo()); } } else { Node name = n.getFirstChild(); defineName(name, n, parent, (info != null) ? info : name.getJSDocInfo()); } } /** * Defines a declared function. */ void defineDeclaredFunction(Node n, Node parent) { assertDefinitionNode(n, Token.FUNCTION); JSDocInfo info = n.getJSDocInfo(); int parentType = parent.getType(); Preconditions.checkState( (scope.isLocal() || parentType != Token.ASSIGN) && parentType != Token.NAME, "function defined as standalone function when it is being " + "assigned"); String functionName = n.getFirstChild().getString(); FunctionType functionType = getFunctionType(functionName, n, info, null); if (NodeUtil.isFunctionDeclaration(n)) { defineSlot(n.getFirstChild(), n, functionType); } } /** * Defines a qualified name assign to an enum or constructor. */ void defineNamedTypeAssign(Node n, Node parent) { assertDefinitionNode(n, Token.ASSIGN); JSDocInfo info = n.getJSDocInfo(); // TODO(nicksantos): We should support direct assignment to a // prototype, as in: // Foo.prototype = { // a: function() { ... }, // b: function() { ... } // }; // Right now (6/23/08), we understand most of this syntax, but we // don't tie the "a" and "b" methods to the context of Foo. Node rvalue = n.getLastChild(); Node lvalue = n.getFirstChild(); info = (info != null) ? info : rvalue.getJSDocInfo(); if (rvalue.getType() == Token.FUNCTION || info != null && info.isConstructor()) { getFunctionType(l

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>value.getQualifiedName(), rvalue, info, lvalue); } else if (info != null && info.hasEnumParameterType()) { JSType type = getEnumType(lvalue.getQualifiedName(), n, rvalue, info.getEnumParameterType().evaluate(scope, typeRegistry)); if (type != null) { setDeferredType(lvalue, type); } } } /** * Defines a variable based on the {@link Token#NAME} node passed. * @param name The {@link Token#NAME} node. * @param var The parent of the {@code name} node, which must be a * {@link Token#VAR} node. * @param parent {@code var}'s parent. * @param info the {@link JSDocInfo} information relating to this * {@code name} node. */ private void defineName(Node name, Node var, Node parent, JSDocInfo info) { Node value = name.getFirstChild(); if (value != null && value.getType() == Token.FUNCTION) { // function String functionName = name.getString(); FunctionType functionType = getFunctionType(functionName, value, info, null); if (functionType.isReturnTypeInferred() && scope.isLocal()) { defineSlot(name, var, null); } else { defineSlot(name, var, functionType); } } else { // variable's type JSType type = null; if (info == null) { // the variable's type will be inferred CompilerInput input = compiler.getInput(sourceName); Preconditions.checkNotNull(input, sourceName); type = input.isExtern() ? getNativeType(UNKNOWN_TYPE) : null; } else if (info.hasEnumParameterType()) { type = getEnumType(name.getString(), var, value, info.getEnumParameterType().evaluate(scope, typeRegistry)); } else if (info.isConstructor()) { type = getFunctionType(name.getString(), value, info, name); } else { type = getDeclaredTypeInAnnotation(sourceName, name, info); } defineSlot(name, var, type); } } /** * Gets the function type from the function node and its attached * {@link JSDocInfo}. * @param name the function's name * @param rValue the function node. It must be a {@link Token#FUNCTION}. * @param info the {@link JSDocInfo} attached to the function definition * @param lvalueNode The node where this function is being * assigned. For example, {@code A.prototype.foo = ...} would be used to * determine that this function is a method of A.prototype. May be * null to indicate that this is not being assigned to a qualified name. */ private FunctionType getFunctionType(String name, Node rValue, JSDocInfo info, @Nullable Node lvalueNode)

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> { FunctionType functionType = null; // Global function aliases should be registered with the type registry. if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) { Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() instanceof FunctionType) { functionType = (FunctionType) var.getType(); if (functionType != null && (functionType.isConstructor() || functionType.isInterface())) { typeRegistry.declareType(name, functionType.getInstanceType()); } } return functionType; } Node owner = null; if (lvalueNode != null) { owner = getPrototypePropertyOwner(lvalueNode); } Node errorRoot = rValue == null ? lvalueNode : rValue; boolean isFnLiteral = rValue != null && rValue.getType() == Token.FUNCTION; Node fnRoot = isFnLiteral ? rValue : null; Node parametersNode = isFnLiteral ? rValue.getFirstChild().getNext() : null; Node fnBlock = isFnLiteral ? parametersNode.getNext() : null; if (functionType == null && info != null && info.hasType()) { JSType type = info.getType().evaluate(scope, typeRegistry); // Known to be not null since we have the FUNCTION token there. type = type.restrictByNotNullOrUndefined(); if (type.isFunctionType()) { functionType = (FunctionType) type; functionType.setJSDocInfo(info); } } if (functionType == null) { // Find the type of any overridden function. FunctionType overriddenPropType = null; if (lvalueNode != null && lvalueNode.getType() == Token.GETPROP && lvalueNode.isQualifiedName()) { Var var = scope.getVar( lvalueNode.getFirstChild().getQualifiedName()); if (var != null) { ObjectType ownerType = ObjectType.cast(var.getType()); if (ownerType != null) { String propName = lvalueNode.getLastChild().getString(); overriddenPropType = findOverriddenFunction(ownerType, propName); } } } functionType = new FunctionTypeBuilder(name, compiler, errorRoot, sourceName, scope) .setSourceNode(fnRoot) .inferFromOverriddenFunction(overriddenPropType, parametersNode) .inferTemplateTypeName(info) .inferReturnType(info) .inferInheritance(info) .inferThisType(info, owner) .inferParameterTypes(parametersNode, info) .inferReturnStatementsAsLastResort(fnBlock) .buildAndRegister(); } // assigning the function type to the function node if (rValue != null) { setDeferredType(rValue, functionType); } // all done return functionType; } /** * Find the function that's being overridden on this type, if any. */ private

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> FunctionType findOverriddenFunction( ObjectType ownerType, String propName) { // First, check to see if the property is implemented // on a superclass. JSType propType = ownerType.getPropertyType(propName); if (propType instanceof FunctionType) { return (FunctionType) propType; } else { // If it's not, then check to see if it's implemented // on an implemented interface. for (ObjectType iface : ownerType.getCtorImplementedInterfaces()) { propType = iface.getPropertyType(propName); if (propType instanceof FunctionType) { return (FunctionType) propType; } } } return null; } /** * Gets an enum type. If the definition is correct, the object literal used * to define the enum is traversed to gather the elements name, and this * method checks for duplicates. This method also enforces that all * elements' name be syntactic constants according to the * {@link CodingConvention} used. * * @param name the enum's name such as {@code HELLO} or {@code goog.foo.BAR} * @param value the enum's original value. This value may be {@code null}. * @param parent the value's parent * @param elementsType the type of the elements of this enum * @return the enum type */ private EnumType getEnumType(String name, Node parent, Node value, JSType elementsType) { EnumType enumType = null; // no value with @enum if (value != null) { if (value.getType() == Token.OBJECTLIT) { // collect enum elements enumType = typeRegistry.createEnumType(name, elementsType); // populate the enum type. Node key = value.getFirstChild(); while (key != null) { String keyName = NodeUtil.getStringValue(key); if (enumType.hasOwnProperty(keyName)) { compiler.report(JSError.make(sourceName, key, ENUM_DUP, keyName)); } else if (!codingConvention.isValidEnumKey(keyName)) { compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else { enumType.defineElement(keyName); } key = key.getNext(); } } else if (value.isQualifiedName()) { Var var = scope.getVar(value.getQualifiedName()); if (var != null && var.getType() instanceof EnumType) { enumType = (EnumType) var.getType(); } } } if (enumType == null) { compiler.report(JSError.make(sourceName, parent, ENUM_INITIALIZER)); } else if (scope.isGlobal()) { if (name != null && !name.isEmpty()) { typeRegistry.declareType(name, enumType.getElementsType()); } } return enumType; } /** * Defines a typed variable.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> The defining node will be annotated with the * variable's type or {@code null} if its type is inferred. * @param name the defining node. It must be a {@link Token#NAME}. * @param parent the {@code name}'s parent. * @param type the variable's type. It may be {@code null}, in which case * the variable's type will be inferred. */ private void defineSlot(Node name, Node parent, JSType type) { defineSlot(name, parent, type, type == null); } /** * Defines a typed variable. The defining node will be annotated with the * variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualfied names. boolean shouldDeclareOnGlobalThis = false; if (n.getType() == Token.NAME) { Preconditions.checkArgument( parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.LP || parent.getType() == Token.CATCH); shouldDeclareOnGlobalThis = scope.isGlobal() && (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION); } else { Preconditions.checkArgument( n.getType() == Token.GETPROP && (parent.getType() == Token.ASSIGN || parent.getType() == Token.EXPR_RESULT)); } String variableName = n.getQualifiedName(); Preconditions.checkArgument(!variableName.isEmpty()); // If n is a property, then we should really declare it in the // scope where the root object appears. This helps out people // who declare "global" names in an anonymous namespace. Scope scopeToDeclareIn = scope; if (n.getType() == Token.GETPROP && !scope.isGlobal() && isQnameRootedInGlobalScope(n)) { Scope globalScope = scope.getGlobalScope(); // don't try to declare in the global scope if there's // already a symbol there with this name. if (!globalScope.isDeclared(variableName, false)) { scopeToDeclareIn = scope.getGlobalScope(); } } // declared in closest scope? if (scopeToDeclareIn.isDeclared(variableName, false)) { Var oldVar = scopeToDeclareIn.getVar(variableName); validator.expectUndeclaredVariable( sourceName, n, parent, oldVar, variableName, type);

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> } else { if (!inferred) { setDeferredType(n, type); } CompilerInput input = compiler.getInput(sourceName); boolean isExtern = input.isExtern(); Var newVar = scopeToDeclareIn.declare(variableName, n, type, input, inferred); if (shouldDeclareOnGlobalThis) { ObjectType globalThis = typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS); if (inferred) { globalThis.defineInferredProperty(variableName, type == null ? getNativeType(JSTypeNative.NO_TYPE) : type, isExtern); } else { globalThis.defineDeclaredProperty(variableName, type, isExtern); } } // We need to do some additional work for constructors and interfaces. if (type instanceof FunctionType && // We don't want to look at empty function types. !type.isEmptyType()) { FunctionType fnType = (FunctionType) type; if ((fnType.isConstructor() || fnType.isInterface()) && !fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) { // Declare var.prototype in the scope chain. FunctionType superClassCtor = fnType.getSuperClassConstructor(); scopeToDeclareIn.declare(variableName + ".prototype", n, fnType.getPrototype(), input, /* declared iff there's an explicit supertype */ superClassCtor == null || superClassCtor.getInstanceType().equals( getNativeType(OBJECT_TYPE))); // Make sure the variable is initialized to something. if (newVar.getInitialValue() == null && !isExtern) { compiler.report( JSError.make(sourceName, n, fnType.isConstructor() ? CTOR_INITIALIZER : IFACE_INITIALIZER, variableName)); } } } } } /** * Check if the given node is a property of a name in the global scope. */ private boolean isQnameRootedInGlobalScope(Node n) { Node root = NodeUtil.getRootOfQualifiedName(n); if (root.getType() == Token.NAME) { Var var = scope.getVar(root.getString()); if (var != null) { return var.isGlobal(); } } return false; } /** * Look for a type declaration on a GETPROP node. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ private JSType getDeclaredGetPropType(NodeTraversal t, JSDocInfo info, Node n, @Nullable Node rhsValue) { if (info != null && info.hasType()) { return getDeclared

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>TypeInAnnotation(t, n, info); } else if (info != null && info.hasEnumParameterType()) { return n.getJSType(); } else if (rhsValue != null && rhsValue.getType() == Token.FUNCTION) { return rhsValue.getJSType(); } else { return getDeclaredTypeInAnnotation(t, n, info); } } /** * Look for class-defining calls. * Because JS has no 'native' syntax for defining classes, * this is often very coding-convention dependent and business-logic heavy. */ private void checkForClassDefiningCalls( NodeTraversal t, Node n, Node parent) { SubclassRelationship relationship = codingConvention.getClassesDefinedByCall(n); if (relationship != null) { ObjectType superClass = ObjectType.cast( typeRegistry.getType(relationship.superclassName)); ObjectType subClass = ObjectType.cast( typeRegistry.getType(relationship.subclassName)); if (superClass != null && subClass != null) { FunctionType superCtor = superClass.getConstructor(); FunctionType subCtor = subClass.getConstructor(); if (relationship.type == SubclassType.INHERITS) { validator.expectSuperType(t, n, superClass, subClass); } if (superCtor != null && subCtor != null) { codingConvention.applySubclassRelationship( superCtor, subCtor, relationship.type); } } } String singletonGetterClassName = codingConvention.getSingletonGetterClassName(n); if (singletonGetterClassName != null) { ObjectType objectType = ObjectType.cast( typeRegistry.getType(singletonGetterClassName)); if (objectType != null) { FunctionType functionType = objectType.getConstructor(); if (functionType != null) { FunctionType getterType = typeRegistry.createFunctionType(objectType); codingConvention.applySingletonGetter(functionType, getterType, objectType); } } } DelegateRelationship delegateRelationship = codingConvention.getDelegateRelationship(n); if (delegateRelationship != null) { applyDelegateRelationship(delegateRelationship); } ObjectLiteralCast objectLiteralCast = codingConvention.getObjectLiteralCast(t, n); if (objectLiteralCast != null) { ObjectType type = ObjectType.cast( typeRegistry.getType(objectLiteralCast.typeName)); if (type != null && type.getConstructor() != null) { setDeferredType(objectLiteralCast.objectNode, type); } else { compiler.report(JSError.make(t.getSourceName(), n, CONSTRUCTOR_EXPECTED)); } } } /** * Apply special properties that only apply to delegates. */ private void applyDelegateRelationship( DelegateRelationship delegateRelationship) { ObjectType delegatorObject = ObjectType.cast( typeRegistry.getType(delegateRelationship.delegator)); ObjectType delegateBaseObject = ObjectType.cast( typeRegistry.getType(delegateRelationship.delegateBase)); ObjectType delegate

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>SuperObject = ObjectType.cast( typeRegistry.getType(codingConvention.getDelegateSuperclassName())); if (delegatorObject != null && delegateBaseObject != null && delegateSuperObject != null) { FunctionType delegatorCtor = delegatorObject.getConstructor(); FunctionType delegateBaseCtor = delegateBaseObject.getConstructor(); FunctionType delegateSuperCtor = delegateSuperObject.getConstructor(); if (delegatorCtor != null && delegateBaseCtor != null && delegateSuperCtor != null) { FunctionParamBuilder functionParamBuilder = new FunctionParamBuilder(typeRegistry); functionParamBuilder.addRequiredParams( getNativeType(U2U_CONSTRUCTOR_TYPE)); FunctionType findDelegate = typeRegistry.createFunctionType( typeRegistry.createDefaultObjectUnion(delegateBaseObject), functionParamBuilder.build()); FunctionType delegateProxy = typeRegistry.createConstructorType( delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX, null, null, null); delegateProxy.setPrototypeBasedOn(delegateBaseObject); codingConvention.applyDelegateRelationship( delegateSuperObject, delegateBaseObject, delegatorObject, delegateProxy, findDelegate); delegateProxyPrototypes.add(delegateProxy.getPrototype()); } } } /** * Declare the symbol for a qualified name in the global scope. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param parent The parent of {@code n}. * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { Node ownerNode = n.getFirstChild(); String ownerName = ownerNode.getQualifiedName(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); Preconditions.checkArgument(qName != null && ownerName != null); // Function prototypes are special. // It's a common JS idiom to do: // F.prototype = { ... }; // So if F does not have an explicitly declared super type, // allow F.prototype to be redefined arbitrarily. if ("prototype".equals(propName)) { Var qVar = scope.getVar(qName); if (qVar != null) { if (!qVar.isTypeInferred()) { // Just ignore assigns to declared prototypes. return; } if (qVar.getScope() == scope) { scope.undeclare(qVar); } } } // Precedence of type information on GETPROPs: // 1) @type annnotation / @enum annotation // 2) ASSIGN to FUNCTION literal // 3) @param/@return annotation (with no function literal) // 4

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>) ASSIGN to anything else // // 1 and 3 are declarations, 4 is inferred, and 2 is a declaration iff // the function has not been declared before. // // FUNCTION literals are special because TypedScopeCreator is very smart // about getting as much type information as possible for them. // Determining type for #1 + #2 + #3 JSType valueType = getDeclaredGetPropType(t, info, n, rhsValue); if (valueType == null && rhsValue != null) { // Determining type for #4 valueType = rhsValue.getJSType(); } if (valueType == null) { if (parent.getType() == Token.EXPR_RESULT) { stubDeclarations.add(new StubDeclaration( n, t.getInput() != null && t.getInput().isExtern(), ownerName)); } return; } boolean inferred = true; if (info != null) { // Determining declaration for #1 + #3 inferred = !(info.hasType() || info.hasEnumParameterType() || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred) { // Determining declaration for #2 inferred = !(rhsValue != null && rhsValue.getType() == Token.FUNCTION && !scope.isDeclared(qName, false)); } if (!inferred) { ObjectType ownerType = getObjectSlot(ownerName); if (ownerType != null) { // Only declare this as an official property if it has not been // declared yet. boolean isExtern = t.getInput() != null && t.getInput().isExtern(); if ((!ownerType.hasOwnProperty(propName) || ownerType.isPropertyTypeInferred(propName)) && ((isExtern && !ownerType.isNativeObjectType()) || !ownerType.isInstanceType())) { // If the property is undeclared or inferred, declare it now. ownerType.defineDeclaredProperty(propName, valueType, isExtern); } } // If the property is already declared, the error will be // caught when we try to declare it in the current scope. defineSlot(n, parent, valueType, inferred); } else if (rhsValue != null && rhsValue.getType() == Token.TRUE) { // We declare these for delegate proxy method properties. ObjectType ownerType = getObjectSlot(ownerName); if (ownerType instanceof FunctionType) { JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis(); String delegateName = codingConvention.getDelegateSuperclassName(); JSType delegateType = delegateName == null ? null : typeRegistry.getType(delegateName); if (delegateType != null && ownerTypeOfThis.isSubtype(delegateType)) { defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true); } } } } /** * Find the ObjectType associated with the given slot. * @param slot

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Name The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub delcarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.isExtern; if (scope.isDeclared(qName, false)) { continue; } // If we see a stub property, make sure to register this property // in the type registry. ObjectType ownerType = getObjectSlot(ownerName); ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE); defineSlot(n, parent, unknownType, true); if (ownerType != null && (isExtern || ownerType.isFunctionPrototypeType())) { // If this is a stub for a prototype, just declare it // as an unknown type. These are seen often in externs. ownerType.defineInferredProperty( propName, unknownType, isExtern); } else { typeRegistry.registerPropertyOnType( propName, ownerType == null ? unknownType : ownerType); } } } /** * Collects all declared properties in a function, and * resolves them relative to the global scope. */ private final class CollectProperties extends AbstractShallowStatementCallback { private final ObjectType thisType; CollectProperties(ObjectType thisType) { this.thisType = thisType; } public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.EXPR_RESULT) { Node child = n.getFirstChild(); switch (child.getType()) { case Token.ASSIGN: maybeCollectMember(t, child.getFirstChild(), child, child.getLastChild()); break; case Token.GETPROP: maybeCollectMember(t, child, child, null); break; } } } private void maybeCollectMember(NodeTraversal t, Node member, Node nodeWithJsDocInfo, @Nullable Node value) { JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo(); // Do nothing if there is no JSDoc type info, or // if the node is not a member expression, or // if the member expression is not of the form: this.someProperty. if (

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>info == null || member.getType() != Token.GETPROP || member.getFirstChild().getType() != Token.THIS) { return; } member.getFirstChild().setJSType(thisType); JSType jsType = getDeclaredGetPropType(t, info, member, value); Node name = member.getLastChild(); if (jsType != null && (name.getType() == Token.NAME || name.getType() == Token.STRING)) { thisType.defineDeclaredProperty( name.getString(), jsType, false /* functions with implementations are not in externs */); } } } // end CollectProperties } /** * A stub declaration without any type information. */ private static final class StubDeclaration { private final Node node; private final boolean isExtern; private final String ownerName; private StubDeclaration(Node node, boolean isExtern, String ownerName) { this.node = node; this.isExtern = isExtern; this.ownerName = ownerName; } } /** * A shallow traversal of the global scope to build up all classes, * functions, and methods. */ private final class GlobalScopeBuilder extends AbstractScopeBuilder { private GlobalScopeBuilder(Scope scope) { super(scope); } /** * Visit a node in the global scope, and add anything it declares to the * global symbol table. * * @param t The current traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); switch (n.getType()) { case Token.ASSIGN: // Handle typedefs. checkForOldStyleTypedef(t, n); break; case Token.VAR: // Handle typedefs. if (n.hasOneChild()) { checkForOldStyleTypedef(t, n); checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; } } @Override void maybeDeclareQualifiedName( NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { checkForTypedef(t, n, info); super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue); } /** * Handle typedefs. * @param t The current traversal. * @param candidate A qualified name node. * @param info JSDoc comments. */ private void checkForTypedef( NodeTraversal t, Node candidate, JSDocInfo info) { if (info == null || !info.hasTypedefType()) { return; } String typedef = candidate.getQualifiedName(); if (typedef == null) { return; } // TODO(nicksantos|user): This is a terrible, terrible hack // to bail

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> out on recusive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE)); JSType realType = info.getTypedefType().evaluate(scope, typeRegistry); if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); if (candidate.getType() == Token.GETPROP) { defineSlot(candidate, candidate.getParent(), getNativeType(NO_TYPE), false); } } /** * Handle typedefs. * @param t The current traversal. * @param candidate An ASSIGN or VAR node. */ // TODO(nicksantos): Kill this. private void checkForOldStyleTypedef(NodeTraversal t, Node candidate) { // old-style typedefs String typedef = codingConvention.identifyTypeDefAssign(candidate); if (typedef != null) { // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recusive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE)); JSDocInfo info = candidate.getJSDocInfo(); JSType realType = null; if (info != null && info.getType() != null) { realType = info.getType().evaluate(scope, typeRegistry); } if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); // Duplicate typedefs get handled when we try to register // this typedef in the scope. } } } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're builidng. */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) return; if (n.getType() == Token.LP && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } super.visit(t

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> "\"{0}\" is not a valid JS property name"); static final DiagnosticType XMODULE_REQUIRE_ERROR = DiagnosticType.warning( "JSC_XMODULE_REQUIRE_ERROR", "namespace \"{0}\" provided in module {1} " + "but required in module {2}"); static final DiagnosticType NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR = DiagnosticType.error( "JSC_NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR", "goog.setCssNameMapping only takes an object literal with string values"); static final DiagnosticType BASE_CLASS_ERROR = DiagnosticType.error( "JSC_BASE_CLASS_ERROR", "incorrect use of goog.base: {0}"); /** The root Closure namespace */ static final String GOOG = "goog"; private final AbstractCompiler compiler; private final JSModuleGraph moduleGraph; // The goog.provides must be processed in a deterministic order. private final Map<String, ProvidedName> providedNames = Maps.newTreeMap(); private final List<UnrecognizedRequire> unrecognizedRequires = Lists.newArrayList(); private final Set<String> exportedVariables = Sets.newHashSet(); private final CheckLevel requiresLevel; private final boolean rewriteNewDateGoogNow; ProcessClosurePrimitives(AbstractCompiler compiler, CheckLevel requiresLevel, boolean rewriteNewDateGoogNow) { this.compiler = compiler; this.moduleGraph = compiler.getModuleGraph(); this.requiresLevel = requiresLevel; this.rewriteNewDateGoogNow = rewriteNewDateGoogNow; // goog is special-cased because it is provided in Closure's base library. providedNames.put(GOOG, new ProvidedName(GOOG, null, null, false /* implicit */)); } Set<String> getExportedVariableNames() { return exportedVariables; } @Override public void process(Node externs, Node root) { new NodeTraversal(compiler, this).traverse(root); for (ProvidedName pn : providedNames.values()) { pn.replace(); } if (requiresLevel.isOn()) { for (UnrecognizedRequire r : unrecognizedRequires) { DiagnosticType error; ProvidedName expectedName = providedNames.get(r.namespace); if (expectedName != null && expectedName.firstNode != null) { // The namespace ended up getting provided after it was required. error = LATE_PROVIDE_ERROR; } else { error = MISSING_PROVIDE_ERROR; } compiler.report(JSError.make( r.inputName, r.requireNode, requiresLevel, error, r.namespace)); } } } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: boolean isExpr = parent.getType() == Token.EXPR_RESULT; Node left = n

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.getFirstChild(); if (left.getType() == Token.GETPROP) { Node name = left.getFirstChild(); if (name.getType() == Token.NAME && GOOG.equals(name.getString())) { // For the sake of simplicity, we report code changes // when we see a provides/requires, and don't worry about // reporting the change when we actually do the replacement. String methodName = name.getNext().getString(); if ("base".equals(methodName)) { processBaseClassCall(t, n); } else if (!isExpr) { // All other methods must be called in an EXPR. break; } else if ("require".equals(methodName)) { processRequireCall(t, n, parent); } else if ("provide".equals(methodName)) { processProvideCall(t, n, parent); } else if ("exportSymbol".equals(methodName)) { Node arg = left.getNext(); if (arg.getType() == Token.STRING) { int dot = arg.getString().indexOf('.'); if (dot == -1) { exportedVariables.add(arg.getString()); } else { exportedVariables.add(arg.getString().substring(0, dot)); } } } else if ("addDependency".equals(methodName)) { CodingConvention convention = compiler.getCodingConvention(); List<String> typeDecls = convention.identifyTypeDeclarationCall(n); if (typeDecls != null) { for (String typeDecl : typeDecls) { compiler.getTypeRegistry().forwardDeclareType(typeDecl); } } // We can't modify parent, so just create a node that will // get compiled out. parent.replaceChild(n, Node.newNumber(0)); compiler.reportCodeChange(); } else if ("setCssNameMapping".equals(methodName)) { processSetCssNameMapping(t, n, parent); } } } break; case Token.ASSIGN: case Token.NAME: // If this is an assignment to a provided name, remove the provided // object. handleCandidateProvideDefinition(t, n, parent); break; case Token.FUNCTION: // If this is a declaration of a provided named function, this is an // error. Hosited functions will explode if the're provided. if (t.inGlobalScope() && !NodeUtil.isFunctionExpression(n)) { String name = n.getFirstChild().getString(); ProvidedName pn = providedNames.get(name); if (pn != null) { compiler.report(t.makeError(n, FUNCTION_NAMESPACE_ERROR, name)); } } break; case Token.NEW: trySimplifyNewDate(t, n, parent); break; case Token.GETPROP: if (n.getFirstChild().getType() == Token.NAME && parent.getType() != Token.CALL && parent.getType() != Token.ASSIGN && "

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>goog.base".equals(n.getQualifiedName())) { reportBadBaseClassUse(t, n, "May only be called directly."); } break; } } /** * Handles a goog.require call. */ private void processRequireCall(NodeTraversal t, Node n, Node parent) { Node left = n.getFirstChild(); Node arg = left.getNext(); if (verifyArgument(t, left, arg)) { String ns = arg.getString(); ProvidedName provided = providedNames.get(ns); if (provided == null || !provided.isExplicitlyProvided()) { unrecognizedRequires.add( new UnrecognizedRequire(n, ns, t.getSourceName())); } else { JSModule providedModule = provided.explicitModule; // This must be non-null, because there was an explicit provide. Preconditions.checkNotNull(providedModule); JSModule module = t.getModule(); if (moduleGraph != null && module != providedModule && !moduleGraph.dependsOn(module, providedModule)) { compiler.report( t.makeError(n, XMODULE_REQUIRE_ERROR, ns, providedModule.getName(), module.getName())); } } // Requires should be removed before runtime. The one // exception is if the type has not been provided yet and // errors for broken requires are turned off, in which case, // we will be doing a later pass that may error, so we can // leave this here this time and let it error next time if it // is still not provided. if (provided != null || requiresLevel.isOn()) { parent.detachFromParent(); compiler.reportCodeChange(); } } } /** * Handles a goog.provide call. */ private void processProvideCall(NodeTraversal t, Node n, Node parent) { Node left = n.getFirstChild(); Node arg = left.getNext(); if (verifyProvide(t, left, arg)) { String ns = arg.getString(); if (providedNames.containsKey(ns)) { ProvidedName previouslyProvided = providedNames.get(ns); if (!previouslyProvided.isExplicitlyProvided()) { previouslyProvided.addProvide(parent, t.getModule(), true); } else { compiler.report( t.makeError(n, DUPLICATE_NAMESPACE_ERROR, ns)); } } else { registerAnyProvidedPrefixes(ns, parent, t.getModule()); providedNames.put( ns, new ProvidedName(ns, parent, t.getModule(), true)); } } } /** * Handles a candidate definition for a goog.provided name. */ private void handleCandidateProvideDefinition( NodeTraversal t, Node n, Node parent) { if (t.inGlobalScope()) { String name = null; if (n.getType() == Token.NAME && parent.getType() == Token.VAR) { name = n.getString(); } else if

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> (n.getType() == Token.ASSIGN && parent.getType() == Token.EXPR_RESULT) { name = n.getFirstChild().getQualifiedName(); } if (name != null) { if (parent.getBooleanProp(Node.IS_NAMESPACE)) { processProvideFromPreviousPass(t, name, parent); } else { ProvidedName pn = providedNames.get(name); if (pn != null) { pn.addDefinition(parent, t.getModule()); } } } } } /** * Processes the base class call. */ private void processBaseClassCall(NodeTraversal t, Node n) { // Two things must hold for every goog.base call: // 1) We must be calling it on "this". // 2) We must be calling it on a prototype method of the same name as // the one we're in, OR we must be calling it from a constructor. // If both of those things are true, then we can rewrite: // <pre> // function Foo() { // goog.base(this); // } // goog.inherits(Foo, BaseFoo); // Foo.prototype.bar = function() { // goog.base(this, 'bar', 1); // }; // </pre> // as the easy-to-optimize: // <pre> // function Foo() { // BaseFoo.call(this); // } // goog.inherits(Foo, BaseFoo); // Foo.prototype.bar = function() { // Foo.superClass_.bar.call(this, 1); // }; // // Most of the logic here is just to make sure the AST's // structure is what we expect it to be. Node callee = n.getFirstChild(); Node thisArg = callee.getNext(); if (thisArg == null || thisArg.getType() != Token.THIS) { reportBadBaseClassUse(t, n, "First argument must be 'this'."); return; } Node enclosingFnNameNode = getEnclosingDeclNameNode(t); if (enclosingFnNameNode == null) { reportBadBaseClassUse(t, n, "Could not find enclosing method."); return; } String enclosingQname = enclosingFnNameNode.getQualifiedName(); if (enclosingQname.indexOf(".prototype.") == -1) { // Handle constructors. Node enclosingParent = enclosingFnNameNode.getParent(); Node maybeInheritsExpr = (enclosingParent.getType() == Token.ASSIGN ? enclosingParent.getParent() : enclosingParent).getNext(); Node baseClassNode = null; if (maybeInheritsExpr != null && maybeInheritsExpr.getType() == Token.EXPR_RESULT && maybeInheritsExpr.getFirstChild().getType() == Token.CALL) { Node callNode = maybeInheritsExpr.getFirstChild();

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if ("goog.inherits".equals( callNode.getFirstChild().getQualifiedName()) && callNode.getLastChild().isQualifiedName()) { baseClassNode = callNode.getLastChild(); } } if (baseClassNode == null) { reportBadBaseClassUse( t, n, "Could not find goog.inherits for base class"); return; } // We're good to go. n.replaceChild( callee, NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), String.format("%s.call", baseClassNode.getQualifiedName()), callee, "goog.base")); compiler.reportCodeChange(); } else { // Handle methods. Node methodNameNode = thisArg.getNext(); if (methodNameNode == null || methodNameNode.getType() != Token.STRING) { reportBadBaseClassUse(t, n, "Second argument must name a method."); return; } String methodName = methodNameNode.getString(); String ending = ".prototype." + methodName; if (enclosingQname == null || !enclosingQname.endsWith(ending)) { reportBadBaseClassUse( t, n, "Enclosing method does not match " + methodName); return; } // We're good to go. Node className = enclosingFnNameNode.getFirstChild().getFirstChild(); n.replaceChild( callee, NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), String.format("%s.superClass_.%s.call", className.getQualifiedName(), methodName), callee, "goog.base")); n.removeChild(methodNameNode); compiler.reportCodeChange(); } } /** * Returns the qualified name node of the function whose scope we're in, * or null if it cannot be found. */ private Node getEnclosingDeclNameNode(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); if (NodeUtil.isFunctionDeclaration(scopeRoot)) { // function x() {...} return scopeRoot.getFirstChild(); } else { Node parent = scopeRoot.getParent(); if (parent != null) { if (parent.getType() == Token.ASSIGN || parent.getLastChild() == scopeRoot && parent.getFirstChild().isQualifiedName()) { // x.y.z = function() {...}; return parent.getFirstChild(); } else if (parent.getType() == Token.NAME) { // var x = function() {...}; return parent; } } } return null; } /** Reports an incorrect use of super-method calling. */ private void reportBadBaseClassUse( NodeTraversal t, Node n, String extraMessage) { compiler.report(t.makeError(n, BASE_CLASS_ERROR, extraMessage)); } /** * Processes the output of processed-provide from a previous pass. This will * update our data structures in the same manner as

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if the provide had been * processed in this pass. */ private void processProvideFromPreviousPass( NodeTraversal t, String name, Node parent) { if (!providedNames.containsKey(name)) { // Record this provide created on a previous pass, and create a dummy // EXPR node as a placeholder to simulate an explicit provide. Node expr = new Node(Token.EXPR_RESULT); expr.copyInformationFromForTree(parent); parent.getParent().addChildBefore(expr, parent); compiler.reportCodeChange(); JSModule module = t.getModule(); registerAnyProvidedPrefixes(name, expr, module); ProvidedName provided = new ProvidedName(name, expr, module, true); providedNames.put(name, provided); provided.addDefinition(parent, module); } else { // Remove this provide if it came from a previous pass since we have an // replacement already. if (isNamespacePlaceholder(parent)) { parent.getParent().removeChild(parent); compiler.reportCodeChange(); } } } /** * Processes a call to goog.setCssNameMapping(). Either the argument to * goog.setCssNameMapping() is valid, in which case it will be used to create * a CssRenamingMap for the compiler of this CompilerPass, or it is invalid * and a JSCompiler error will be reported. * @see #visit(NodeTraversal, Node, Node) */ private void processSetCssNameMapping(NodeTraversal t, Node n, Node parent) { Node left = n.getFirstChild(); Node arg = left.getNext(); if (verifyArgument(t, left, arg, Token.OBJECTLIT)) { // Translate OBJECTLIT into SubstitutionMap. All keys and // values must be strings, or an error will be thrown. final Map<String, String> cssNames = Maps.newHashMap(); JSError error = null; for (Node key = arg.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); if (key.getType() != Token.STRING || value == null || value.getType() != Token.STRING) { error = t.makeError(n, NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR); } if (error != null) { compiler.report(error); break; } cssNames.put(key.getString(), value.getString()); } // If there were no errors, create a CssRenamingMap from cssNames, update // the compiler to use it and remove the call to goog.setCssNameMapping(). if (error == null) { CssRenamingMap cssRenamingMap = new CssRenamingMap() { public String get(String value) { if (cssNames.containsKey(value)) { return cssNames.get(value); } else { return value; } } }; compiler.setCssRenamingMap

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>(cssRenamingMap); parent.getParent().removeChild(parent); compiler.reportCodeChange(); } } } /** * Try to simplify "new Date(goog.now())" to "new Date()". */ private void trySimplifyNewDate(NodeTraversal t, Node n, Node parent) { if (!rewriteNewDateGoogNow) { return; } Preconditions.checkArgument(n.getType() == Token.NEW); Node date = n.getFirstChild(); if (!NodeUtil.isName(date) || !"Date".equals(date.getString())) { return; } Node callGoogNow = date.getNext(); if (callGoogNow == null || !NodeUtil.isCall(callGoogNow) || callGoogNow.getNext() != null) { return; } Node googNow = callGoogNow.getFirstChild(); String googNowQName = googNow.getQualifiedName(); if (googNowQName == null || !"goog.now".equals(googNowQName) || googNow.getNext() != null) { return; } n.removeChild(callGoogNow); compiler.reportCodeChange(); } /** * Verifies that a provide method call has exactly one argument, * and that it's a string literal and that the contents of the string are * valid JS tokens. Reports a compile error if it doesn't. * * @return Whether the argument checked out okay */ private boolean verifyProvide(NodeTraversal t, Node methodName, Node arg) { if (!verifyArgument(t, methodName, arg)) { return false; } for (String part : arg.getString().split("\\.")) { if (!NodeUtil.isValidPropertyName(part)) { compiler.report(t.makeError(arg, INVALID_PROVIDE_ERROR, part)); return false; } } return true; } /** * Verifies that a method call has exactly one argument, and that it's a * string literal. Reports a compile error if it doesn't. * * @return Whether the argument checked out okay */ private boolean verifyArgument(NodeTraversal t, Node methodName, Node arg) { return verifyArgument(t, methodName, arg, Token.STRING); } /** * Verifies that a method call has exactly one argument, and that it is of the * desired type. Reports a compile error if it doesn't. * * @return Whether the argument checked out okay */ private boolean verifyArgument(NodeTraversal t, Node methodName, Node arg, int desiredType) { DiagnosticType diagnostic = null; if (arg == null) { diagnostic = NULL_ARGUMENT_ERROR; } else if (arg.getType() != desiredType) { diagnostic = INVALID_ARGUMENT_ERROR; } else if (arg.getNext() != null) { diagnostic = TOO_MANY_ARGUMENTS_ERROR; } if (diagnostic != null) { compiler.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>report( t.makeError(methodName, diagnostic, methodName.getQualifiedName())); return false; } return true; } /** * Registers ProvidedNames for prefix namespaces if they haven't * already been defined. The prefix namespaces must be registered in * order from shortest to longest. * * @param ns The namespace whose prefixes may need to be provided. * @param node The EXPR of the provide call. * @param module The current module. */ private void registerAnyProvidedPrefixes( String ns, Node node, JSModule module) { int pos = ns.indexOf('.'); while (pos != -1) { String prefixNs = ns.substring(0, pos); pos = ns.indexOf('.', pos + 1); if (providedNames.containsKey(prefixNs)) { providedNames.get(prefixNs).addProvide( node, module, false /* implicit */); } else { providedNames.put( prefixNs, new ProvidedName(prefixNs, node, module, false /* implicit */)); } } } // ------------------------------------------------------------------------- /** * Information required to replace a goog.provide call later in the traversal. */ private class ProvidedName { private final String namespace; // The node and module where the call was explicitly or implicitly // goog.provided. private final Node firstNode; private final JSModule firstModule; // The node where the call was explicitly goog.provided. May be null // if the namespace is always provided implicitly. private Node explicitNode = null; private JSModule explicitModule = null; // The candidate definition. private Node candidateDefinition = null; // The minimum module where the provide must appear. private JSModule minimumModule = null; // The replacement declaration. private Node replacementNode = null; ProvidedName(String namespace, Node node, JSModule module, boolean explicit) { Preconditions.checkArgument( node == null /* The base case */ || NodeUtil.isExpressionNode(node)); this.namespace = namespace; this.firstNode = node; this.firstModule = module; addProvide(node, module, explicit); } /** * Add an implicit or explicit provide. */ void addProvide(Node node, JSModule module, boolean explicit) { if (explicit) { Preconditions.checkState(explicitNode == null); Preconditions.checkArgument(NodeUtil.isExpressionNode(node)); explicitNode = node; explicitModule = module; } updateMinimumModule(module); } boolean isExplicitlyProvided() { return explicitNode != null; } /** * Record function declaration, variable declaration or assignment that * refers to the same name as the provide statement. Give preference to * declarations; if no declation exists record a reference to an * assignment so it repurposed later. */ void addDefinition(Node node, JSModule module) { Preconditions.checkArgument(NodeUtil.isExpressionNode(node) || //

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> assign NodeUtil.isFunction(node) || NodeUtil.isVar(node)); Preconditions.checkArgument(explicitNode != node); if ((candidateDefinition == null) || !NodeUtil.isExpressionNode(node)) { candidateDefinition = node; updateMinimumModule(module); } } private void updateMinimumModule(JSModule newModule) { if (minimumModule == null) { minimumModule = newModule; } else if (moduleGraph != null) { minimumModule = moduleGraph.getDeepestCommonDependencyInclusive( minimumModule, newModule); } else { // If there is no module graph, then there must be exactly one // module in the program. Preconditions.checkState(newModule == minimumModule, "Missing module graph"); } } /** * Replace the provide statement. * * If we're providing a name with no definition, then create one. * If we're providing a name with a duplicate definition, then make sure * that definition becomes a declaration. */ void replace() { if (firstNode == null) { // Don't touch the base case ('goog'). replacementNode = candidateDefinition; return; } // Handle the case where there is a duplicate definition for an explicitly // provided symbol. if (candidateDefinition != null && explicitNode != null) { explicitNode.detachFromParent(); compiler.reportCodeChange(); // Does this need a VAR keyword? replacementNode = candidateDefinition; if (NodeUtil.isExpressionNode(candidateDefinition)) { candidateDefinition.putBooleanProp(Node.IS_NAMESPACE, true); Node assignNode = candidateDefinition.getFirstChild(); Node nameNode = assignNode.getFirstChild(); if (nameNode.getType() == Token.NAME) { // Need to convert this assign to a var declaration. Node valueNode = nameNode.getNext(); assignNode.removeChild(nameNode); assignNode.removeChild(valueNode); nameNode.addChildToFront(valueNode); Node varNode = new Node(Token.VAR, nameNode); varNode.copyInformationFrom(candidateDefinition); candidateDefinition.getParent().replaceChild( candidateDefinition, varNode); nameNode.setJSDocInfo(assignNode.getJSDocInfo()); compiler.reportCodeChange(); replacementNode = varNode; } } } else { // Handle the case where there's not a duplicate definition. replacementNode = createDeclarationNode(); if (firstModule == minimumModule) { firstNode.getParent().addChildBefore(replacementNode, firstNode); } else { // In this case, the name was implicitly provided by two independent // modules. We need to move this code up to a common module. int indexOfDot = namespace.lastIndexOf('.'); if (indexOfDot == -1) { // Any old place is fine. compiler.getNodeForCodeInsertion(minimumModule) .addChildToBack(replacementNode); } else { // Add it after the parent

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> namespace. ProvidedName parentName = providedNames.get(namespace.substring(0, indexOfDot)); Preconditions.checkNotNull(parentName); Preconditions.checkNotNull(parentName.replacementNode); parentName.replacementNode.getParent().addChildAfter( replacementNode, parentName.replacementNode); } } if (explicitNode != null) { explicitNode.detachFromParent(); } compiler.reportCodeChange(); } } /** * Create the declaration node for this name, without inserting it * into the AST. */ private Node createDeclarationNode() { if (namespace.indexOf('.') == -1) { return makeVarDeclNode(namespace, firstNode); } else { return makeAssignmentExprNode(namespace, firstNode); } } /** * Creates a simple namespace variable declaration * (e.g. <code>var foo = {};</code>). * * @param namespace A simple namespace (must be a valid js identifier) * @param sourceNode The node to get source information from. */ private Node makeVarDeclNode(String namespace, Node sourceNode) { Node name = Node.newString(Token.NAME, namespace); name.addChildToFront(createNamespaceLiteral()); Node decl = new Node(Token.VAR, name); decl.putBooleanProp(Node.IS_NAMESPACE, true); // TODO(nicksantos): ew ew ew. Create a mutator package. if (compiler.getCodingConvention().isConstant(namespace)) { name.putBooleanProp(Node.IS_CONSTANT_NAME, true); } Preconditions.checkState(isNamespacePlaceholder(decl)); decl.copyInformationFromForTree(sourceNode); return decl; } /** * There are some special cases where clients of the compiler * do not run TypedScopeCreator after running this pass. * So always give the namespace literal a type. */ private Node createNamespaceLiteral() { Node objlit = new Node(Token.OBJECTLIT); objlit.setJSType( compiler.getTypeRegistry().createAnonymousObjectType()); return objlit; } /** * Creates a dotted namespace assignment expression * (e.g. <code>foo.bar = {};</code>). * * @param namespace A dotted namespace * @param node A node from which to copy source info. */ private Node makeAssignmentExprNode(String namespace, Node node) { Node decl = new Node(Token.EXPR_RESULT, new Node(Token.ASSIGN, NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), namespace, node, namespace), createNamespaceLiteral())); decl.putBooleanProp(Node.IS_NAMESPACE, true); Preconditions.checkState(isNamespacePlaceholder(decl)); decl.copyInformationFromForTree(node); return decl; } } /** * @return Whether the node is namespace placeholder. */ private static boolean isNamespacePlaceholder(Node n) { if (!n

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>.getBooleanProp(Node.IS_NAMESPACE)) { return false; } Node value = null; if (n.getType() == Token.EXPR_RESULT) { Node assign = n.getFirstChild(); value = assign.getLastChild(); } else if (n.getType() == Token.VAR) { Node name = n.getFirstChild(); value = name.getFirstChild(); } return value != null && value.getType() == Token.OBJECTLIT && !value.hasChildren(); } // ------------------------------------------------------------------------- /** * Information required to create a {@code MISSING_PROVIDE_ERROR} warning. */ private class UnrecognizedRequire { final Node requireNode; final String namespace; final String inputName; UnrecognizedRequire(Node requireNode, String namespace, String inputName) { this.requireNode = requireNode; this.namespace = namespace; this.inputName = inputName; } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> can be collapsed, and the * node traversal may give them to us again) */ private final Set<Node> nodesToCollapse = Sets.newHashSet(); CollapseVariableDeclarations(AbstractCompiler compiler) { Preconditions.checkState(!compiler.isNormalized()); this.compiler = compiler; } public void process(Node externs, Node root) { collapses.clear(); nodesToCollapse.clear(); NodeTraversal.traverse(compiler, root, new CombinedCompilerPass(compiler, new ExploitAssigns(), new GatherCollapses())); if (!collapses.isEmpty()) { applyCollapses(); compiler.reportCodeChange(); } } private class ExploitAssigns extends AbstractPostOrderCallback { public void visit(NodeTraversal t, Node expr, Node exprParent) { if (!NodeUtil.isExprAssign(expr)) { return; } collapseAssign(t, expr.getFirstChild(), expr, exprParent); } /** * Try to collapse the given assign into subsequent expressions. */ private void collapseAssign(NodeTraversal t, Node assign, Node expr, Node exprParent) { Node leftValue = assign.getFirstChild(); Node rightValue = leftValue.getNext(); if (isCollapsibleValue(leftValue, true) && collapseAssignEqualTo(expr, exprParent, leftValue)) { t.getCompiler().reportCodeChange(); } else if (isCollapsibleValue(rightValue, false) && collapseAssignEqualTo(expr, exprParent, rightValue)) { t.getCompiler().reportCodeChange(); } else if (rightValue.getType() == Token.ASSIGN) { // Recursively deal with nested assigns. collapseAssign(t, rightValue, expr, exprParent); } } /** * Determines whether we know enough about the given value to be able * to collapse it into subsequent expressions. * * For example, we can collapse booleans and variable names: * <code> * x = 3; y = x; // y = x = 3; * a = true; b = true; // b = a = true; * <code> * But we won't try to collapse complex expressions. * * @param value The value node. * @param isLValue Whether it's on the left-hand side of an expr. */ private boolean isCollapsibleValue(Node value, boolean isLValue) { switch (value.getType()) { case Token.GETPROP: // Do not collapse GETPROPs on arbitrary objects, because // they may be implemented setter functions, and oftentimes // setter functions fail on native objects. This is ok for "THIS" // objects, because we assume that they are non-native. return !isLValue || value.getFirstChild().getType() == Token.THIS; case Token.NAME: case Token.NUMBER: case Token.TRUE: case Token.FALSE: case Token.NULL:

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> case Token.STRING: return true; } return false; } /** * Collapse the given assign expression into the expression directly * following it, if possible. * * @param expr The expression that may be moved. * @param exprParent The parent of {@code expr}. * @param value The value of this expression, expressed as a node. Each * expression may have multiple values, so this function may be called * multiple times for the same expression. For example, * <code> * a = true; * </code> * is equal to the name "a" and the boolean "true". * @return Whether the expression was collapsed succesfully. */ private boolean collapseAssignEqualTo(Node expr, Node exprParent, Node value) { Node assign = expr.getFirstChild(); Node parent = exprParent; Node next = expr.getNext(); while (next != null) { switch (next.getType()) { case Token.AND: case Token.OR: case Token.HOOK: case Token.IF: case Token.RETURN: case Token.EXPR_RESULT: // Dive down the left side parent = next; next = next.getFirstChild(); break; case Token.VAR: if (next.getFirstChild().hasChildren()) { parent = next.getFirstChild(); next = parent.getFirstChild(); break; } return false; case Token.GETPROP: case Token.NAME: if (next.isQualifiedName()) { String nextName = next.getQualifiedName(); if (value.isQualifiedName() && nextName.equals(value.getQualifiedName())) { // If the previous expression evaluates to value of a // qualified name, and that qualified name is used again // shortly, then we can exploit the assign here. // Verify the assignment doesn't change its own value. if (!isSafeReplacement(next, assign)) { return false; } exprParent.removeChild(expr); expr.removeChild(assign); parent.replaceChild(next, assign); return true; } } return false; case Token.NUMBER: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: if (value.getType() == next.getType()) { if ((next.getType() == Token.STRING || next.getType() == Token.NUMBER) && !next.isEquivalentTo(value)) { return false; } // If the r-value of the expr assign is an immutable value, // and the value is used again shortly, then we can exploit // the assign here. exprParent.removeChild(expr); expr.removeChild(assign); parent.replaceChild(next, assign); return true; } return false; case Token.ASSIGN: // Assigns are really tricky. In lots of cases, we want to inline // into the right side of the assign.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> But the left side of the // assign is evaluated first, and it may have convoluted logic: // a = null; // (a = b).c = null; // We don't want to exploit the first assign. Similarly: // a.b = null; // a.b.c = null; // We don't want to exploit the first assign either. // // To protect against this, we simply only inline when the left side // is guaranteed to evaluate to the same L-value no matter what. Node leftSide = next.getFirstChild(); if (leftSide.getType() == Token.NAME || leftSide.getType() == Token.GETPROP && leftSide.getFirstChild().getType() == Token.THIS) { // Dive down the right side of the assign. parent = next; next = leftSide.getNext(); break; } else { return false; } default: // Return without inlining a thing return false; } } return false; } } /** * Checks name referenced in node to determine if it might have * changed. * @return Whether the replacement can be made. */ private boolean isSafeReplacement(Node node, Node replacement) { // No checks are needed for simple names. if (node.getType() == Token.NAME) { return true; } Preconditions.checkArgument(node.getType() == Token.GETPROP); Node name = node.getFirstChild(); if (name.getType() == Token.NAME && isNameAssignedTo(name.getString(), replacement)) { return false; } return true; } /** * @return Whether name is assigned in the expression rooted at node. */ private boolean isNameAssignedTo(String name, Node node) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { if (isNameAssignedTo(name, c)) { return true; } } if (node.getType() == Token.NAME) { Node parent = node.getParent(); if (parent.getType() == Token.ASSIGN && parent.getFirstChild() == node) { if (name.equals(node.getString())) { return true; } } } return false; } /** * Gathers all of the variable declarations that should be collapsed into one. * We do not do the collapsing as we go since node traversal would be affected * by the changes we are making to the parse tree. */ private class GatherCollapses extends AbstractPostOrderCallback { public void visit(NodeTraversal t, Node n, Node parent) { // Only care about var nodes if (n.getType() != Token.VAR) return; // If we've already looked at this node, skip it if (nodesToCollapse.contains(n)) return; // Adjacent VAR children of an IF node are the if and else parts and can

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>'t // be collapsed if (parent.getType() == Token.IF) return; Node varNode = n; // Find variable declarations that follow this one (if any) n = n.getNext(); boolean hasNodesToCollapse = false; while (n != null && n.getType() == Token.VAR) { nodesToCollapse.add(n); hasNodesToCollapse = true; n = n.getNext(); } if (hasNodesToCollapse) { nodesToCollapse.add(varNode); collapses.add(new Collapse(varNode, parent)); } } } private void applyCollapses() { for (Collapse collapse : collapses) { Node first = collapse.firstVarNode; while (first.getNext() != null && first.getNext().getType() == Token.VAR) { Node next = collapse.parent.removeChildAfter(first); // Move all children of the next var node into the first one. first.addChildrenToBack(next.removeChildren()); } } } }

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>L; private enum Kind { ORDINARY, CONSTRUCTOR, INTERFACE } /** * {@code [[Call]]} property. */ private ArrowType call; /** * The {@code prototype} property. This field is lazily initialized by * {@code #getPrototype()}. The most important reason for lazily * initializing this field is that there are cycles in the native types * graph, so some prototypes must temporarily be {@code null} during * the construction of the graph. */ private FunctionPrototypeType prototype; /** * Whether a function is a constructor, an interface, or just an ordinary * function. */ private final Kind kind; /** * The type of {@code this} in the scope of this function. */ private ObjectType typeOfThis; /** * The function node which this type represents. It may be {@code null}. */ private Node source; /** * The interfaces directly implemented by this function. * It is only relevant for constructors. May not be {@code null}. */ private List<ObjectType> implementedInterfaces = ImmutableList.of(); /** * The types which are subtypes of this function. It is only relevant for * constructors and may be {@code null}. */ private List<FunctionType> subTypes; /** * The template type name. May be {@code null}. */ private String templateTypeName; /** Creates an instance for a function that might be a constructor. */ FunctionType(JSTypeRegistry registry, String name, Node source, ArrowType arrowType, ObjectType typeOfThis, String templateTypeName, boolean isConstructor, boolean nativeType) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), nativeType); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkNotNull(arrowType); this.source = source; this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY; if (isConstructor) { this.typeOfThis = typeOfThis != null && typeOfThis.isNoObjectType() ? typeOfThis : new InstanceObjectType(registry, this, nativeType); } else { this.typeOfThis = typeOfThis != null ? typeOfThis : registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); } this.call = arrowType; this.templateTypeName = templateTypeName; } /** Creates an instance for a function that is an interface. */ private FunctionType(JSTypeRegistry registry, String name, Node source) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE)); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkArgument(name != null); this.source = source; this.call = new ArrowType(registry, new Node(Token.LP), null); this.kind = Kind.INTERFACE; this.

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> if ("prototype".equals(name)) { return getPrototype(); } else { if (!hasOwnProperty(name)) { if ("call".equals(name)) { // Define the "call" function lazily. Node params = getParametersNode(); if (params == null) { // If there's no params array, don't do any type-checking // in this CALL function. defineDeclaredProperty(name, new FunctionBuilder(registry) .withReturnType(getReturnType()) .build(), false); } else { params = params.cloneTree(); Node thisTypeNode = Node.newString(Token.NAME, "thisType"); thisTypeNode.setJSType( registry.createOptionalNullableType(getTypeOfThis())); params.addChildToFront(thisTypeNode); thisTypeNode.setOptionalArg(true); defineDeclaredProperty(name, new FunctionBuilder(registry) .withParamsNode(params) .withReturnType(getReturnType()) .build(), false); } } else if ("apply".equals(name)) { // Define the "apply" function lazily. FunctionParamBuilder builder = new FunctionParamBuilder(registry); // Ecma-262 says that apply's second argument must be an Array // or an arguments object. We don't model the arguments object, // so let's just be forgiving for now. // TODO(nicksantos): Model the Arguments object. builder.addOptionalParams( registry.createNullableType(getTypeOfThis()), registry.createNullableType( registry.getNativeType(JSTypeNative.OBJECT_TYPE))); defineDeclaredProperty(name, new FunctionBuilder(registry) .withParams(builder) .withReturnType(getReturnType()) .build(), false); } } return super.getPropertyType(name); } } @Override boolean defineProperty(String name, JSType type, boolean inferred, boolean inExterns) { if ("prototype".equals(name)) { ObjectType objType = type.toObjectType(); if (objType != null) { if (objType.isEquivalentTo(prototype)) { return true; } return setPrototype( new FunctionPrototypeType( registry, this, objType, isNativeObjectType())); } else { return false; } } return super.defineProperty(name, type, inferred, inExterns); } @Override public boolean isPropertyTypeInferred(String property) { return "prototype".equals(property) || super.isPropertyTypeInferred(property); } @Override public JSType getLeastSupertype(JSType that) { return supAndInfHelper(that, true); } @Override public JSType getGreatestSubtype(JSType that) { return supAndInfHelper(that, false); } /** * Computes the supremum or infimum of functions with other types. * Because sup() and

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> // broad. The greatest function takes var_args None parameters, which // means that all parameters register a type warning. // // Instead, we use the U2U ctor type, which has unknown type args. FunctionType greatestFn = registry.getNativeFunctionType(JSTypeNative.U2U_CONSTRUCTOR_TYPE); FunctionType leastFn = registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE); return leastSuper ? greatestFn : leastFn; } return leastSuper ? super.getLeastSupertype(that) : super.getGreatestSubtype(that); } /** * Try to get the sup/inf of two functions by looking at the * piecewise components. */ private FunctionType tryMergeFunctionPiecewise( FunctionType other, boolean leastSuper) { Node newParamsNode = null; if (call.hasEqualParameters(other.call)) { newParamsNode = call.parameters; } else { // If the parameters are not equal, don't try to merge them. // Someday, we should try to merge the individual params. return null; } JSType newReturnType = leastSuper ? call.returnType.getLeastSupertype(other.call.returnType) : call.returnType.getGreatestSubtype(other.call.returnType); ObjectType newTypeOfThis = null; if (isEquivalent(typeOfThis, other.typeOfThis)) { newTypeOfThis = typeOfThis; } else { JSType maybeNewTypeOfThis = leastSuper ? typeOfThis.getLeastSupertype(other.typeOfThis) : typeOfThis.getGreatestSubtype(other.typeOfThis); if (maybeNewTypeOfThis instanceof ObjectType) { newTypeOfThis = (ObjectType) maybeNewTypeOfThis; } else { newTypeOfThis = leastSuper ? registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) : registry.getNativeObjectType(JSTypeNative.NO_OBJECT_TYPE); } } boolean newReturnTypeInferred = call.returnTypeInferred || other.call.returnTypeInferred; return new FunctionType( registry, null, null, new ArrowType( registry, newParamsNode, newReturnType, newReturnTypeInferred), newTypeOfThis, null, false, false); } /** * Given a constructor or an interface type, get its superclass constructor * or {@code null} if none exists. */ public FunctionType getSuperClassConstructor() { Preconditions.checkArgument(isConstructor() || isInterface()); ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return null; } return maybeSuperInstanceType.getConstructor(); } /** * Given a constructor or an interface type, find out whether the unknown * type is a supertype of the current type. */ public boolean hasUnknownSupertype() { Preconditions.checkArgument(isConstructor() || isInterface()); Preconditions.check

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS>Argument(!this.isUnknownType()); // Potential infinite loop if our type system messes up or someone defines // a bad type. Otherwise the loop should always end. FunctionType ctor = this; while (true) { ObjectType maybeSuperInstanceType = ctor.getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return false; } if (maybeSuperInstanceType.isUnknownType()) { return true; } ctor = maybeSuperInstanceType.getConstructor(); if (ctor == null) { return false; } Preconditions.checkState(ctor.isConstructor() || ctor.isInterface()); } } /** * Given a constructor or an interface type and a property, finds the * top-most superclass that has the property defined (including this * constructor). */ public JSType getTopMostDefiningType(String propertyName) { Preconditions.checkState(isConstructor() || isInterface()); Preconditions.checkArgument(getPrototype().hasProperty(propertyName)); FunctionType ctor = this; JSType topInstanceType; do { topInstanceType = ctor.getInstanceType(); ctor = ctor.getSuperClassConstructor(); } while (ctor != null && ctor.getPrototype().hasProperty(propertyName)); return topInstanceType; } /** * Two function types are equal if their signatures match. Since they don't * have signatures, two interfaces are equal if their names match. */ @Override public boolean isEquivalentTo(JSType otherType) { if (!(otherType instanceof FunctionType)) { return false; } FunctionType that = (FunctionType) otherType; if (!that.isFunctionType()) { return false; } if (this.isConstructor()) { if (that.isConstructor()) { return this == that; } return false; } if (this.isInterface()) { if (that.isInterface()) { return this.getReferenceName().equals(that.getReferenceName()); } return false; } if (that.isInterface()) { return false; } return this.typeOfThis.isEquivalentTo(that.typeOfThis) && this.call.isEquivalentTo(that.call); } @Override public int hashCode() { return isInterface() ? getReferenceName().hashCode() : call.hashCode(); } public boolean hasEqualCallType(FunctionType otherType) { return this.call.isEquivalentTo(otherType.call); } /** * Informally, a function is represented by * {@code function (params): returnType} where the {@code params} is a comma * separated list of types, the first one being a special * {@code this:T} if the function expects a known type for {@code this}. */ @Override public String toString() { if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) { return "Function"; } StringBuilder b

Closure, 88

<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB> } return false; } // The current liveness of the variable private enum VariableLiveness { MAYBE_LIVE, // May be still live in the current expression tree. READ, // Known there is a read left of it. KILL, // Known there is a write before any read. } /** * Give an expression and a variable. It returns READ, if the first * reference of that variable is a read. It returns KILL, if the first * reference of that variable is an assignment. It returns MAY_LIVE otherwise. */ private VariableLiveness isVariableReadBeforeKill( Node n, String variable) { if (NodeUtil.isName(n) && variable.equals(n.getString())) { if (NodeUtil.isLhs(n, n.getParent())) { <CHANGES> <CHANGEE> // The expression to which the assignment is made is evaluated before // the RHS is evaluated (normal left to right evaluation) but the KILL // occurs after the RHS is evaluated. <CHANGES> <CHANGEE> return VariableLiveness.KILL; } else { return VariableLiveness.READ; } } // Expressions are evaluated left-right, depth first. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION VariableLiveness state = isVariableReadBeforeKill(child, variable); if (state != VariableLiveness.MAYBE_LIVE) { return state; <FILEE> <SCANS> is non-restrictive. // In practical terms, if C implements I, and I has a method m, // then any m doesn't necessarily have to C#m's 'this' // type doesn't need to match I. (other.typeOfThis.getConstructor() != null && other.typeOfThis.getConstructor().isInterface()) || // If one of the 'this' types is covariant of the other, // then we'll treat them as covariant (see comment above). other.typeOfThis.isSubtype(this.typeOfThis) || this.typeOfThis.isSubtype(other.typeOfThis); return treatThisTypesAsCovariant && this.call.isSubtype(other.call); } return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that); } @Override public <T> T visit(Visitor<T> visitor) { return visitor.caseFunctionType(this); } /** * Gets the type of instance of this function. * @throws IllegalStateException if this function is not a constructor * (see {@link #isConstructor()}). */ public ObjectType getInstanceType() { Preconditions.checkState(hasInstanceType()); return typeOfThis; } /** Sets the instance type. This should only be used for special native types. */ void setInstanceType(ObjectType instanceType) { typeOfThis = instanceType; } /** * Returns whether this function type has an instance type. */ public boolean hasInstanceType() { return isConstructor() || isInterface(); } /** * Gets the type of {@code this} in this function. */ public ObjectType getTypeOfThis() { return typeOfThis.isNoObjectType() ? registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) : typeOfThis; } /** * Gets the source node or null if this is an unknown function. */ public Node getSource() { return source; } /** * Sets the source node. */ public void setSource(Node source) { this.source = source; } /** Adds a type to the list of subtypes for this type. */ private void addSubType(FunctionType subType) { if (subTypes == null) { subTypes = Lists.newArrayList(); } subTypes.add(subType); } /** * Returns a list of types that are subtypes of this type. This is only valid * for constructor functions, and may be null. This allows a downward * traversal of the subtype graph. */ public List<FunctionType> getSubTypes() { return subTypes; } @Override public boolean hasCachedValues() { return prototype != null || super.hasCachedValues(); } /** * Gets the template type name. */ public String getTemplateTypeName() { return templateTypeName; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {